@jspg-ai/coding-bb 0.0.3-beta.37 → 0.0.3-beta.39

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.
@@ -4,7 +4,7 @@ trigger: always_on
4
4
 
5
5
  # 业务空间开发纪律(双模式,用户选择制)
6
6
 
7
- **仅当工作目录是业务空间根(存在 `CBB-WORKSPACE.json` 且 `.git` 为目录)时生效。空间支持两种开发模式,能力对称(都能修改 workspace 内容与应用代码、都强制分支隔离、交付均有 MR 与直合并两条通道,ADR 0007/0008),由用户显式选择,agent 只给推荐不代决。**
7
+ **仅当处于业务空间体系(空间根、`.worktrees/` 工作树、及其中各应用检出)时生效。空间支持两种开发模式,能力对称(都能修改 workspace 内容与应用代码、都强制分支隔离、交付均有 MR 与直合并两条通道,ADR 0007/0008),由用户显式选择,agent 只给推荐不代决。**
8
8
 
9
9
  **模式一 · 本地模式(单线程快改,当前会话就地干):**
10
10
 
@@ -25,12 +25,14 @@ trigger: always_on
25
25
  - **收尾合并门禁(ADR 0007)**:两模式收尾均须每仓库"已合并"(`safety-check` / `merge-branches.js --check` 判定);未合并三选一——提 MR / 直合并 / 确认废弃(远程重命名 `<分支>-deprecated`);交付完成自动删本地+远程分支
26
26
  - **模式误判零代价**:本地模式中途要并行/隔离 → 重跑 `cbb-worktree-init`(分支已存在自动复用,提交随身带走)
27
27
 
28
- **三时刻对齐(agent 必须遵守):**
28
+ **五时刻对齐(agent 必须遵守):**
29
29
 
30
30
  1. **开工前——状态自检**(`cbb-local-mode` 的 `check-local-state.js`):干净态零交互直接开工;**遗留态(别的需求未收尾,任一仓库检出在非主干分支)必须让用户四选一**(继续 / 先收尾 / 升级 worktree / 丢弃),不得自行决定
31
31
  2. **开工时——布局确认**:向用户展示参与仓库与分支名,确认后执行
32
32
  3. **进行中——插单归属**:用户插入非本需求的改动,先确认归属(并入 / 收尾后处理 / 转 worktree),不得静默混入
33
+ 4. **打包前——主干对齐**(任何构建打包动作前):对产物所涉及的仓库(按代码来源判定:哪个仓库的代码被编进产物就同步哪个,与产物存放位置无关;多仓聚合产物则全部同步)先对齐再打包——① `git fetch <远端>`;② 在当前需求分支执行 `git merge <远端>/<主干>`(远端通常为 `origin`,主干名以远端 HEAD 为准,常见 main / master;在 main 检出上打包时改为 `git pull`;工作区未提交修改阻碍合并时,先提交或暂存再对齐);③ 冲突未解决时**禁止打包**,停下报告用户;fetch/merge 失败 → 报告并等待用户指示;④ 对齐完成后(由此产生的 merge 提交属正常)再执行打包命令(`mvn package` / `npm run build` / 前端 bundle / 生成部署包等凡产出构建产物者);产物目录须已被 `.gitignore` 忽略(未忽略先补 ignore——该修改属普通变更,随需求分支提交)。产物基线 = 主干最新,避免基于过时分支打包导致的集成返工;**不 rebase、不 force push**,merge 提交随下次 push 一并推送
34
+ 5. **打包后——产物收集与留存**:打包完成后把各仓库产物**复制**到 `<空间根>/.build/<需求名>/<应用名>/`(原件保持原位;需求名 = 分支名;workspace 仓库产物用 `workspace` 子目录;目标目录已存在时先向用户确认清理或覆盖;`.build/` 由 cbb 写入空间根 `.gitignore`)——产物按需求聚合留存,worktree 关闭后不丢失;打包时顺带报告 `.build/` 积压现状(需求目录数与总体积),建议清理已交付或已废弃需求的产物目录,经用户逐项确认后执行、**永不自动删除**
33
35
 
34
36
  **读基准代码约定:** 本地模式进行中,工作区是开发态而非基准——要读"main 现状"先确认检出分支,或直接 `git show main:<path>` / `git diff main`;不要默认工作区即基准。
35
37
 
36
- **检验标准:** 任何 main 检出上无载荷提交(hook 兜底,git log 可机械审计;merge 提交除外);本地模式收尾后全部仓库回主干且 clean;worktree 模式的变更产物只出现在需求工作树内;需求分支只经 push 技能产生;**未合并且非废弃的分支无法通过收尾(门禁兜底)**。
38
+ **检验标准:** 任何 main 检出上无载荷提交(hook 兜底,git log 可机械审计;merge 提交除外);本地模式收尾后全部仓库回主干且 clean;worktree 模式的**变更(代码改动)**只出现在需求工作树内(打包产物按第 5 条收集到空间根 `.build/`,不在此限);需求分支只经 push 技能产生;**未合并且非废弃的分支无法通过收尾(门禁兜底)**。
@@ -113,16 +113,8 @@ function hookOpts(adapter) {
113
113
  return adapter.hookDialect ? { dialect: adapter.hookDialect } : {};
114
114
  }
115
115
 
116
- // 工具历史名迁移:beta.16 及以前为 'codebuddy'(CodeBuddy 目录模型),
117
- // beta.17 起统一为 'workbuddy'。存量 .last-tools / 清单里的旧名读取时归一,
118
- // 下次安装由 saveLastTools 以新名落盘。
119
- const LEGACY_TOOL_NAMES = { codebuddy: 'workbuddy' };
120
-
121
- /** 工具名集合归一(旧名 → 新名);null 透传 */
122
- function normalizeToolSet(set) {
123
- if (!set) return set;
124
- return new Set([...set].map(t => LEGACY_TOOL_NAMES[t] || t));
125
- }
116
+ // 工具历史名迁移(codebuddy → workbuddy)与集合归一见 setup-decision.js
117
+ // (normalizeToolSet / LEGACY_TOOL_NAMES 单一来源)
126
118
 
127
119
  /**
128
120
  * 迁移清理:移除本包写在旧用户级 settings 里的 hook(如 WorkBuddy 适配修正:
@@ -251,7 +243,7 @@ function readLastTools() {
251
243
  if (!fs.existsSync(filePath)) return null;
252
244
  try {
253
245
  const data = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
254
- return normalizeToolSet(new Set(data.tools || []));
246
+ return setupDecision.normalizeToolSet(new Set(data.tools || []));
255
247
  } catch (_) {
256
248
  return null;
257
249
  }
@@ -1388,7 +1380,7 @@ function decideUpgradeIfInstalled(targetDir, selectedAdapters) {
1388
1380
  const pkg = require(path.join(SHARED_STANDARDS_DIR, 'package.json'));
1389
1381
  const decision = setupDecision.decideSetupAction({
1390
1382
  sameVersion: version.compareVersions(installed, pkg.version) === 0,
1391
- lastTools: normalizeToolSet(setupDecision.readLastToolsFrom(targetDir)),
1383
+ lastTools: setupDecision.normalizeToolSet(setupDecision.readLastToolsFrom(targetDir)),
1392
1384
  selectedNames: (selectedAdapters || []).map(a => a.name),
1393
1385
  });
1394
1386
 
@@ -1795,11 +1787,16 @@ async function setupWorkspace() {
1795
1787
  if (!process.stdin.isTTY) {
1796
1788
  selectedAdapters = selectable;
1797
1789
  } else {
1790
+ // 预选 = 所选空间 .cbb/.last-tools 记录的并集(目录规则与 initSingleWorkspace 一致:
1791
+ // repo 末段子目录;退市工具不并回;旧名归一;并集为空回退预选 opencode——首次引导行为)
1792
+ const records = selected.map(w => {
1793
+ const subdirName = workspaces.extractRepoBasename(w.repo);
1794
+ return subdirName ? setupDecision.readLastToolsFrom(path.join(parentDir, subdirName)) : null;
1795
+ });
1798
1796
  const toolIdx = await checkbox.promptCheckbox({
1799
1797
  header: '🔧 选择要安装的 AI 工具(空格切换,回车确认)',
1800
1798
  items: selectable.map(a => ({ label: c('cyan', a.displayName) })),
1801
- // 默认只预选 opencode(团队当前主用),可空格加选其他工具
1802
- preSelected: selectable.reduce((acc, a, i) => (a.name === 'opencode' ? acc.concat(i) : acc), []),
1799
+ preSelected: setupDecision.preselectIndexes(records, selectable),
1803
1800
  });
1804
1801
  selectedAdapters = toolIdx.size === 0
1805
1802
  ? selectable
@@ -1,12 +1,16 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * cbb setup 空间级安装的跳过判定(纯逻辑,可单测)。
4
+ * cbb setup 空间级安装的跳过判定 + 工具记录归一与多空间预选(纯逻辑,可单测)。
5
5
  *
6
6
  * 背景 bug(0.0.3-beta.15 修复):旧版 decideUpgradeIfInstalled 只比较
7
7
  * 「已装版本 vs 当前包版本」,版本一致即 skip——导致「空间已是当前版本 +
8
8
  * 本次新选了工具(如 WorkBuddy)」被整体跳过,新工具的空间级目录
9
9
  * (.workbuddy/ 等)永不生成,.last-tools 也不更新。
10
+ *
11
+ * 背景 bug(0.0.3-beta.39 修复):多空间 `cbb setup` 工具面板预选曾硬编码
12
+ * 「只预选 opencode」,未读所选空间的 .last-tools——直接回车会把已装工具
13
+ * 当作「未勾选」由清单差集清理移除。现由 preselectIndexes 计算并集预选。
10
14
  */
11
15
 
12
16
  const fs = require('fs');
@@ -45,4 +49,44 @@ function decideSetupAction({ sameVersion, lastTools, selectedNames }) {
45
49
  return { action: 'skip', newTools: [], reason: 'up-to-date' };
46
50
  }
47
51
 
48
- module.exports = { decideSetupAction, readLastToolsFrom };
52
+ // ── 工具记录归一与多空间预选 ─────────────────────────────────
53
+
54
+ // 工具历史名迁移:beta.16 及以前为 'codebuddy'(CodeBuddy 目录模型),
55
+ // beta.17 起统一为 'workbuddy'。存量 .last-tools / 清单里的旧名读取时归一,
56
+ // 下次安装由 saveLastTools 以新名落盘。
57
+ const LEGACY_TOOL_NAMES = { codebuddy: 'workbuddy' };
58
+
59
+ /** 工具名集合归一(旧名 → 新名);null 透传 */
60
+ function normalizeToolSet(set) {
61
+ if (!set) return set;
62
+ return new Set([...set].map(t => LEGACY_TOOL_NAMES[t] || t));
63
+ }
64
+
65
+ /**
66
+ * 由多个空间 .last-tools 记录计算多空间 setup 面板的预选索引。
67
+ *
68
+ * 并集语义:任一所选空间装过的工具都预勾(延续既有工具面,批量升级一次回车);
69
+ * 退市工具不并回(ADR 0006);并集过滤后为空时回退预选 fallbackName(首次引导行为)。
70
+ *
71
+ * @param {Array<Set<string>|null>} records 各空间记录(null = 无记录)
72
+ * @param {Array<{name:string}>} selectable 面板可选项(未退市适配器,顺序即索引)
73
+ * @param {string} [fallbackName='opencode'] 并集为空时的回退预选工具名
74
+ * @returns {number[]} 预选索引(升序)
75
+ */
76
+ function preselectIndexes(records, selectable, fallbackName = 'opencode') {
77
+ const union = new Set();
78
+ (records || []).forEach(r => {
79
+ if (!r) return;
80
+ normalizeToolSet(r).forEach(t => union.add(t));
81
+ });
82
+ const valid = new Set(selectable.map(a => a.name));
83
+ const kept = [...union].filter(t => valid.has(t));
84
+ if (kept.length > 0) {
85
+ const target = new Set(kept);
86
+ return selectable.map((a, i) => (target.has(a.name) ? i : -1)).filter(i => i >= 0);
87
+ }
88
+ const idx = selectable.findIndex(a => a.name === fallbackName);
89
+ return idx >= 0 ? [idx] : [];
90
+ }
91
+
92
+ module.exports = { decideSetupAction, readLastToolsFrom, LEGACY_TOOL_NAMES, normalizeToolSet, preselectIndexes };
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * 安装时:若 .gitignore 已含某目录(认可带/不带尾斜杠两种形式),则不重复添加;
7
7
  * 否则新增无尾斜杠形式(.claude / .qoder,更通用)。.cbb/ / .codespace/ /
8
- * .worktrees/ 这类纯目录路径维持带斜杠。
8
+ * .worktrees/ / .build/ 这类纯目录路径维持带斜杠。
9
9
  * 卸载时:移除本包曾写入的所有形式(新旧版本均覆盖)。
10
10
  *
11
11
  * 注意:.gitignore 条目无 _managedBy 之类的标记,卸载按"形式匹配"移除,
@@ -32,6 +32,8 @@ const GITIGNORE_RULES = [
32
32
  { add: '.trae', accept: ['.trae', '.trae/'] },
33
33
  // .minimax:MiniMax Code 项目级配置根(rules / skills)
34
34
  { add: '.minimax', accept: ['.minimax', '.minimax/'] },
35
+ // .build/:0.0.3-beta.38 新增 —— 打包产物收集目录(空间纪律第 5 条:产物按需求聚合到 .build/<需求名>/)
36
+ { add: '.build/', accept: ['.build/', '.build'] },
35
37
  ];
36
38
 
37
39
  /** 将 .gitignore 文本拆为行数组。 */
@@ -413,6 +413,8 @@ git -C "<ws_root>" worktree list
413
413
 
414
414
  目标 worktree 应已不在列表中。
415
415
 
416
+ **产物留存检查(打包产物 · 不自动删除)**:检查 `<ws_root>/.build/<branch>/` 是否存在(`test -d`),并统计 `.build/` 全景(需求目录数与总体积,Node 或 `du -sh` 等价实现)。**关闭流程绝不自动删除 `.build/` 内容**(打包产物是交付后的取件来源,删错不可逆);用户明确表示不需要时,才按其指定范围清理。
417
+
416
418
  **工作区回根交接(必须执行)**:若用户的开发会话工作在 worktree 内(当前会话工作目录 = 已删除的 `<wt_path>`),此刻目录已悬空——**OpenCode 等支持会话搬移的工具由 agent 自动 `session_move` 切回 `<ws_root>`,不列入用户待办**;无该能力的工具才提示用户把工作区切回 `<ws_root>`(打开文件夹 / Open Folder / 切换工作目录),并在最终报告中给出该绝对路径。
417
419
 
418
420
  ---
@@ -436,6 +438,9 @@ git -C "<ws_root>" worktree list
436
438
  - 交付完成 → 已删除本地 + 远程分支:feature-xxx(逐仓库列出)
437
439
  - 已废弃 → 远程已重命名为 feature-xxx-deprecated(本地分支保留原名)
438
440
  剩余工作空间 worktree:<列出仍存在的,或 "无" >
441
+ 产物留存:.build/<branch>/(已保留;不需要可告诉我清理)
442
+ .build 全景:共 N 个需求目录 / ~X MB(可顺带清理已交付或已废弃需求)
443
+ (产物留支行仅在该目录存在时显示)
439
444
  👉 仅当所用工具不支持自动切根、且你的窗口仍停在已删除的 worktree 时,请手动切回工作空间根:<ws_root 绝对路径>
440
445
  Workspace cleaned up.
441
446
  ```
@@ -462,6 +467,7 @@ git -C "<ws_root>" worktree list
462
467
  - 操作 `.worktrees/worktree-<需求名>` 以外的任何 worktree(如 `~/.qoder/worktree/...` 会话 worktree)
463
468
  - 移除/删除前跳过 Step 2 安全评估
464
469
  - 删除工作空间根目录的 AI 配置目录(`.claude/`、`.qoder/`、`.opencode/` 等)
470
+ - 未经用户确认删除 `.build/` 内的打包产物(留存目录的清理须用户逐项确认;关闭流程绝不自动删)
465
471
  - 用 `AskUserQuestion` 收集自由文本(分支名等直接对话提问)
466
472
  - 手动改任何仓库的 `.gitignore`(关闭流程不碰它)
467
473
  - 运行环境 node 不可用时强行继续 → 改为 fail-fast 提示用户
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jspg-ai/coding-bb",
3
- "version": "0.0.3-beta.37",
3
+ "version": "0.0.3-beta.39",
4
4
  "description": "整合业界热门且高价值的工具、框架与技能,为 AI CODING AGENT 提供统一的行为准则与工作流,辅助开发者将需求高效落地为符合规范的代码",
5
5
  "main": "cbb/lib/install/init.js",
6
6
  "bin": {