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

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.
@@ -101,18 +101,28 @@ trigger: always_on
101
101
 
102
102
  ## 8. 业务空间目录纪律
103
103
 
104
- **仅当工作目录是业务空间根(存在 `workspace-config.json`)时生效:写操作命令必须落在需求工作树内。**
104
+ **仅当工作目录是业务空间根(存在 `workspace-config.json`)时生效。空间根支持两种开发模式,接到需求先判定本次属于哪种,再动手。**
105
105
 
106
- 业务空间根(主分支)只负责组织与调度,不做需求开发。接到需求先建工作树(`cbb-worktree-init` skill,或斜杠命令 `/cbb-worktree-init`),之后:
106
+ **模式一 · worktree 模式(默认;凡涉及应用代码改动必走此路):**
107
+
108
+ 接到需求先建工作树(`cbb-worktree-init` skill,或斜杠命令 `/cbb-worktree-init`),之后:
107
109
 
108
110
  - 一切产生写操作的命令(git commit / openspec / 构建 / 测试 / 文件修改)必须以 `.worktrees/worktree-<需求名>/`(或其应用子目录)为工作目录:显式 `cd` 或绝对路径
109
- - 禁止在空间根执行写操作:不在主分支提交代码、不在空间根创建 openspec 变更产物、不改 `.codespace/` 基准代码
110
- - 只读命令(git status / log、查看文件)在空间根执行无妨
111
+ - 不在空间根主分支提交应用代码、不在空间根创建本模式的 openspec 变更产物
111
112
  - 会话开始时确认本次需求对应的工作树路径;存在多个未完成工作树时,先与用户确认目标,不要猜
112
113
 
113
- **会话与工作目录是两回事:** AI 会话窗口始终停在**空间根**(worktree 内不装 AI 配置,无工作流命令),`/opsx:*` 等命令仍在空间根会话发起;只是**产生写操作的工作目录**指向需求工作树(显式 `cd` 或绝对路径)。不要把会话切到 worktree 内,也不要因「在 worktree 内开发」就在空间根直接动手。
114
+ **模式二 · 轻量模式(仅限空间根仓库自身内容:openspec 产物、docs、空间配置):**
115
+
116
+ 需求完全不触碰任何应用代码文件时,可直接在空间根开发:变更产物落空间根 `openspec/`,走完整 opsx 流程后提交 main 分支,**提交后即 push——main 就是交付线,无 worktree、无 MR,不得悬空本地提交**。
117
+
118
+ **两模式共同红线(无例外):**
119
+
120
+ - `.codespace/` 是工具维护的基准代码,不是开发区——任何模式下都不许在其中改应用代码
121
+ - 只读命令(git status / log、查看文件)在空间根执行无妨
122
+
123
+ **会话与工作目录是两回事:** AI 会话窗口始终停在**空间根**(worktree 内不装 AI 配置,无工作流命令),`/opsx:*` 等命令仍在空间根会话发起;worktree 模式下**产生写操作的工作目录**指向需求工作树(显式 `cd` 或绝对路径)。不要把会话切到 worktree 内。
114
124
 
115
- **检验标准:** 空间根主分支的 `git status` 永远干净(除工具维护的配置文件外),openspec 变更产物只出现在需求工作树内。
125
+ **检验标准:** worktree 模式的变更产物只出现在需求工作树内;空间根 main 的 `git status` 干净(除工具维护的配置文件外),且轻量模式产生的 main 提交都已 push(本地 main 不长期领先 origin/main)。
116
126
 
117
127
  ---
118
128
 
@@ -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 能在拉好仓库后把目标目录
@@ -1246,28 +1247,41 @@ async function init(targetDir, options = {}) {
1246
1247
  // ── cbb setup 命令 ──────────────────────────────────────
1247
1248
 
1248
1249
  /**
1249
- * 若 targetDir 已安装,比较已装版本与当前包版本决定是否继续安装。
1250
- * - 版本一致 → 返回 'skip'(不询问、不重复安装)
1251
- * - 版本不同 → 返回 'install'(setup 已整体确认过,直接升级到当前包版本,不再二次询问)
1252
- * - 未安装 返回 'install'(不提示,直接继续)
1250
+ * 若 targetDir 已安装,比较「已装版本 vs 当前包版本」与「本次工具选择 vs .last-tools」
1251
+ * 决定是否继续安装(判定纯逻辑见 setup-decision.js)。
1252
+ * - 版本不同 → 'install'(setup 已整体确认过,直接升级到当前包版本,不再二次询问)
1253
+ * - 版本一致但本次选择含 .last-tools 之外的新工具(或无记录)→ 'install'
1254
+ * (beta.14 及以前的 bug:版本一致即整体跳过,新工具的空间级目录永不安装)
1255
+ * - 版本一致且所选 ⊆ .last-tools → 'skip'(不询问、不重复安装)
1253
1256
  *
1254
1257
  * 注:只比较「已装版本 vs 当前包版本」——安装动作只能落盘当前包的产物,
1255
1258
  * registry 是否有更新由用户级版本检查 hook 提醒,不在此处联网查询。
1259
+ * @param {string} targetDir
1260
+ * @param {Array<{name:string}>} [selectedAdapters] 本次 setup 选中的工具
1256
1261
  * @returns {'install'|'skip'}
1257
1262
  */
1258
- function decideUpgradeIfInstalled(targetDir) {
1263
+ function decideUpgradeIfInstalled(targetDir, selectedAdapters) {
1259
1264
  const installed = version.readInstalledVersion(targetDir);
1260
1265
  if (!installed) return 'install'; // 未安装:不提示,直接继续
1261
1266
 
1262
1267
  const pkg = require(path.join(SHARED_STANDARDS_DIR, 'package.json'));
1268
+ const decision = setupDecision.decideSetupAction({
1269
+ sameVersion: version.compareVersions(installed, pkg.version) === 0,
1270
+ lastTools: setupDecision.readLastToolsFrom(targetDir),
1271
+ selectedNames: (selectedAdapters || []).map(a => a.name),
1272
+ });
1263
1273
 
1264
- // 版本未变化 不处理(不询问、不重复安装)
1265
- if (version.compareVersions(installed, pkg.version) === 0) {
1274
+ if (decision.action === 'skip') {
1266
1275
  step('✓', '版本', `v${installed}(已是最新,跳过)`);
1267
1276
  return 'skip';
1268
1277
  }
1269
-
1270
- step('✓', '版本', `v${installed} → v${pkg.version}`);
1278
+ if (decision.reason === 'new-tools') {
1279
+ step('✓', '版本', `v${installed}(版本一致,新增工具 ${decision.newTools.join('、')} → 继续安装)`);
1280
+ } else if (decision.reason === 'no-tools-record') {
1281
+ step('✓', '版本', `v${installed}(无工具选择记录 → 继续安装)`);
1282
+ } else {
1283
+ step('✓', '版本', `v${installed} → v${pkg.version}`);
1284
+ }
1271
1285
  return 'install';
1272
1286
  }
1273
1287
 
@@ -1558,8 +1572,8 @@ async function initSingleWorkspace(w, opts = {}) {
1558
1572
  console.log(` ${c('gray', `${names} → 编辑 workspace-config.json 填入真实仓库地址`)}`);
1559
1573
  }
1560
1574
 
1561
- // 5. 若已安装:版本未变化直接跳过;有新版本直接升级(setup 已整体确认过,不再二次询问)
1562
- const decision = decideUpgradeIfInstalled(targetDir);
1575
+ // 5. 若已安装:版本一致且无新增工具才跳过;否则继续安装/升级(setup 已整体确认过,不再二次询问)
1576
+ const decision = decideUpgradeIfInstalled(targetDir, opts.selectedAdapters);
1563
1577
  if (decision === 'skip') {
1564
1578
  return { ok: true, subdir: subdirName, upToDate: true };
1565
1579
  }
@@ -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
+ * (.codebuddy/ 等)永不生成,.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 };
@@ -227,7 +227,7 @@ apply:
227
227
  **收尾(全部任务翻成 `- [x]` 后执行;本段为项目级硬约束,覆盖官方 apply 模板自带的完成提示语):**
228
228
 
229
229
  1. **未决项自检**(逐项确认,任何一项未过都不得宣布完成):
230
- - change 产物(proposal.md / design.md / specs/** / tasks.md / plan.md)已提交到需求分支;未提交时先询问用户是否现在提交(可随 cbb-worktree-push 一并推送)
230
+ - change 产物(proposal.md / design.md / specs/** / tasks.md / plan.md)已提交到需求分支(轻量模式:空间根 main 分支);未提交时先询问用户是否现在提交(可随 cbb-worktree-push 一并推送;轻量模式提交后即 push)
231
231
  - 本会话产生的临时文件与环境改动(备份目录、未回写远端的本地提交、cherry-pick 等)已列清并交用户决策
232
232
  2. **输出顺序纪律**:存在未决项时,本轮只列未决项并等待用户决策;**禁止同轮输出"下一步建议"**,禁止出现"规划与实施均已完成""可运行 archive"之类表述(产物未提交时归档会丢规划产物)
233
233
  3. **全部未决项关闭后,唯一可建议的下一步是 `/opsx:verify`**:本项目工作流为 propose → apply → verify → archive,verify(强制 code-review + 产出 cr.md)是 archive 的前置阶段;**禁止直接建议 archive**,官方模板的 "You can archive this change" 提示语以本段为准
@@ -253,3 +253,5 @@ verify:
253
253
  - **路径 B · 先归档后推送**:立即 `/opsx:archive` 落定变更产物(归档移动与 spec 同步发生在 feature 分支上)→ 再执行 `cbb-worktree-push` 一并推送 → 提 MR 到测试分支验收。注意:验收若发现问题,产物已归档,修复需走新 change 或将归档移回后继续;适合改动小、验收把握大的场景
254
254
 
255
255
  用户选定后按该路径执行;官方模板的单一 "suggest archive" 提示语以本段为准。
256
+
257
+ **轻量模式变体**(本次 change 完全不涉及应用代码,产物在空间根仓库):无 worktree push / 测试分支 MR 环节,两条路径替换为——A(推荐)"先 push main 交付、观察无回归后再 archive" / B"立即 archive 落定产物,再 push main";main 提交不得悬空本地。
@@ -5,12 +5,12 @@
5
5
 
6
6
  ## 核心约定
7
7
 
8
- 1. **本目录不做需求开发**。本目录(业务空间主分支)只负责组织与调度;需求开发的**写操作**都在 `.worktrees/worktree-<需求名>/` 隔离目录内进行,**AI 会话仍停在空间根**(worktree 内不装 AI 配置)。
8
+ 1. **双模式开发**:涉及**应用代码**的需求在 `.worktrees/worktree-<需求名>/` 隔离目录内进行(worktree 模式,默认);完全不触碰应用代码的需求(openspec 产物、docs、空间配置)可直接在空间根开发(轻量模式,提交 main 后即 push)。**AI 会话始终停在空间根**(worktree 内不装 AI 配置)。
9
9
  2. **AI 配置安装在空间根**:编码规范、OpenSpec 命令、worktree 管理技能都装在本目录(`cbb setup` / `cbb update` 安装);worktree 内**不安装** AI 配置,AI 会话始终以空间根为基础。
10
- 3. **接到新需求先建 worktree**:使用 `/cbb-worktree-init <需求名>`(skill 形态,各工具写法见文末),或直接自然语言说"为 <需求名> 创建工作空间"。它会为 `workspace-config.json` 中的全量关联应用同步创建同名 worktree。
11
- 4. **开发命令在本会话执行,写操作指向 worktree**:需求提案 / 实现 / 验证 / 归档(`/opsx:propose` → `/opsx:apply` → `/opsx:verify` → `/opsx:archive`)在空间根会话执行;但所有产生写操作的命令(git commit / openspec / 构建 / 测试 / 文件修改)必须以 `.worktrees/worktree-<需求名>/`(或其应用子目录)为工作目录(显式 `cd` 或绝对路径);**禁止在空间根(主分支)执行写操作**。
10
+ 3. **涉及应用代码的新需求先建 worktree**:使用 `/cbb-worktree-init <需求名>`(skill 形态,各工具写法见文末),或直接自然语言说"为 <需求名> 创建工作空间"。它会为 `workspace-config.json` 中的全量关联应用同步创建同名 worktree。
11
+ 4. **开发命令在本会话执行,写操作按模式定落点**:需求提案 / 实现 / 验证 / 归档(`/opsx:propose` → `/opsx:apply` → `/opsx:verify` → `/opsx:archive`)在空间根会话执行;worktree 模式下所有产生写操作的命令(git commit / openspec / 构建 / 测试 / 文件修改)必须以 `.worktrees/worktree-<需求名>/`(或其应用子目录)为工作目录(显式 `cd` 或绝对路径),**不得往主分支提交应用代码**;轻量模式在空间根直接写、提交 main 后即 push。
12
12
  5. **关联应用增减只改配置**:编辑 `workspace-config.json` 的 `apps` 数组(`name` / `repo` / `side` / `desc`),然后用同一需求名重跑 worktree 初始化即幂等补齐;不要手工 `git clone` 应用仓库。
13
- 6. **不要绕过 worktree 直接修改 `.codespace/` 或本目录的代码**。`.codespace/` 是工具维护的基准代码,不是开发区。
13
+ 6. **不要绕过 worktree 直接修改 `.codespace/`**。`.codespace/` 是工具维护的基准代码,不是开发区;应用代码改动任何模式都必须走 worktree。
14
14
 
15
15
  ## 目录结构与职责
16
16
 
@@ -19,7 +19,7 @@
19
19
  | `workspace-config.json` | 关联应用清单,所有应用联动的唯一数据源 | 人工编辑 |
20
20
  | `.codespace/` | 各关联应用的基准代码(每个应用一个子目录) | `cbb setup` 与 worktree 流程自动 clone / fetch;**勿手动编辑**;已 gitignore,不提交 |
21
21
  | `.worktrees/` | 需求隔离开发区(每个需求一个 `worktree-<需求名>/` 目录) | worktree 命令自动创建 / 清理;不提交(首次执行 worktree 流程时自动加入 .gitignore) |
22
- | `openspec/` | OpenSpec 工作流配置(`config.yaml` + `schemas/`) | 由 cbb 安装;需求变更产物(`openspec/changes/` 等)在**需求工作树内**生成,随需求分支提交 |
22
+ | `openspec/` | OpenSpec 工作流配置(`config.yaml` + `schemas/`) | 由 cbb 安装;变更产物(`openspec/changes/` 等)worktree 模式在**需求工作树内**生成随需求分支提交,轻量模式直接在空间根生成提交 main |
23
23
  | `.claude/` `.qoder/` `.opencode/` `.codebuddy/` `.trae/` | AI 工具适配目录:编码规范规则、OpenSpec 命令、worktree 管理技能(skill 形态) | 由 cbb 按 setup 时选择的工具安装;已 gitignore,不提交,勿手动改 |
24
24
  | `.cbb/` | cbb 安装清单与状态(`.managed-by-cbb`、`.last-tools`) | 由 cbb 管理;**勿手动编辑** |
25
25
 
@@ -34,6 +34,8 @@
34
34
  → 全链路完成后说"关闭工作空间"(或 /cbb-worktree-close)
35
35
  ```
36
36
 
37
+ > 轻量模式(需求完全不涉及应用代码)跳过上述流程:空间根直接 `/opsx:*` 全程 → 提交 main → 立即 push。
38
+
37
39
  > worktree 管理三件套(init / close / push)以 **skill 形态**分发:既能被 AI 在对话中按意图**自动触发**,也能用斜杠命令手动指定(`/cbb-worktree-init` 等,各工具一致)。OpenSpec 命令为 `/opsx:propose`(opencode 扁平化为 `/opsx-propose`)。
38
40
 
39
41
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jspg-ai/coding-bb",
3
- "version": "0.0.3-beta.13",
3
+ "version": "0.0.3-beta.15",
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",