@jspg-ai/coding-bb 0.0.3-beta.23 → 0.0.3-beta.24
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.
- package/cbb/dev-standards/rules/cbb-ai-behavior.md +1 -1
- package/cbb/worktrees/skills/cbb-local-mode/SKILL.md +1 -1
- package/cbb/worktrees/skills/cbb-worktree-close/SKILL.md +7 -7
- package/cbb/worktrees/skills/cbb-worktree-close/scripts/discover-apps.js +10 -1
- package/cbb/worktrees/skills/cbb-worktree-close/scripts/remove-worktrees.js +10 -1
- package/cbb/worktrees/skills/cbb-worktree-close/scripts/safety-check.js +10 -1
- package/cbb/worktrees/skills/cbb-worktree-init/SKILL.md +4 -4
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/create-worktrees.js +19 -3
- package/cbb/worktrees/skills/cbb-worktree-push/scripts/commit-worktrees.js +30 -8
- package/cbb/worktrees/skills/cbb-worktree-push/scripts/push-worktrees.js +30 -8
- package/config/workspace-agents.sample.md +1 -1
- package/package.json +1 -1
|
@@ -111,7 +111,7 @@ trigger: always_on
|
|
|
111
111
|
|
|
112
112
|
**模式二 · worktree 模式(并行 / 隔离):**
|
|
113
113
|
|
|
114
|
-
- `cbb-worktree-init` 建隔离 worktree(AI 配置内容子目录已 junction 就位),**开发会话开进 `.worktrees/worktree
|
|
114
|
+
- `cbb-worktree-init` 建隔离 worktree(AI 配置内容子目录已 junction 就位),**开发会话开进 `.worktrees/worktree-<需求名>/`**,可多需求并行;app 检出收拢在 `<wt>/.codespace/<app>/`——**镜像空间根布局**(`.codespace/<app>/...` 相对路径两模式通用,ADR 0003)
|
|
115
115
|
- 根会话保留作控制面(worktree 生灭、`cbb update` 等空间级操作)
|
|
116
116
|
|
|
117
117
|
**基准与防线(两模式共同,ADR 0002):**
|
|
@@ -66,7 +66,7 @@ node "<skill_dir>/scripts/start-local.js" "<ws_root>" "<branch>" --apps "<app1>,
|
|
|
66
66
|
|
|
67
67
|
## Step 3:开发(会话 cwd == 开发目录)
|
|
68
68
|
|
|
69
|
-
- 当前会话**原地**开发:workspace 内容用根相对路径,应用代码用 `.codespace/<app>/...` 相对路径——上游 `/opsx:propose → apply → verify`
|
|
69
|
+
- 当前会话**原地**开发:workspace 内容用根相对路径,应用代码用 `.codespace/<app>/...` 相对路径——上游 `/opsx:propose → apply → verify` 的相对路径指令**原样生效**,零翻译(worktree 模式自 ADR 0003 起同布局,两模式路径通用)
|
|
70
70
|
- 需求产物落 `openspec/changes/<需求名>/`(在分支上,hook 已保证不会误进 main)
|
|
71
71
|
- **读基准代码约定**:本地模式进行中工作区是开发态——要读"main 上是什么"先看 `git -C .codespace/<app> branch --show-current`,或直接 `git show main:<path>`,不要默认工作区即基准
|
|
72
72
|
- **中途插单**:用户插入非本需求的改动 → 先确认归属(并入当前需求 / 收尾后处理 / 转 worktree),不得静默混入
|
|
@@ -13,9 +13,9 @@ description: "关闭/清理/删除需求工作空间的 worktree 和分支。触
|
|
|
13
13
|
|
|
14
14
|
- 本技能的所有原子操作封装为 `scripts/*.js` Node.js 脚本,**跨平台执行**
|
|
15
15
|
- 本技能是 `cbb-worktree-init` 的逆操作:共用 `workspace-config.json`、共用 `.worktrees/worktree-<需求名>/` 物理直聚命名、共用统一的分支名(不加前缀)
|
|
16
|
-
- App worktree **物理嵌套**在 workspace worktree
|
|
16
|
+
- App worktree **物理嵌套**在 workspace worktree 的 `.codespace/` 容器内(`.worktrees/worktree-<需求名>/.codespace/<应用名>/`,镜像空间根布局,ADR 0003),但 git 上属于各自仓库的独立 worktree
|
|
17
17
|
- 只操作 `.worktrees/worktree-<需求名>` 这一类精确路径,**绝不碰其它 worktree**(如 IDE/工具自动创建的 `~/.qoder/worktree/...` 会话 worktree)
|
|
18
|
-
- 通过检查 `.worktrees/worktree
|
|
18
|
+
- 通过检查 `.worktrees/worktree-<需求名>/.codespace/<应用名>/` **是否实际存在**来确定关联应用——创建时用户可能只关联了 `workspace-config.json` 中的部分应用,不能假设全部应用都关联
|
|
19
19
|
- **顺序硬约束:先移除所有 worktree,再删除分支。** 目标分支当前正检出于 worktree 中,先删分支会报 `error: Cannot delete branch '<分支>' checked out at '<worktree 路径>'`
|
|
20
20
|
- **破坏性操作必须先报告、经用户确认才执行**:未提交修改、未合并/未推送的提交,绝不静默 `--force` 或 `-D` 丢弃
|
|
21
21
|
- 软性检查失败时报告详情,询问用户是否继续
|
|
@@ -97,13 +97,13 @@ node "<skill_dir>/scripts/find-target-worktree.js "<ws_root>"
|
|
|
97
97
|
node "<skill_dir>/scripts/discover-apps.js "<ws_root>" "<branch>"
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
-
脚本自动从 `workspace-config.json` 读取应用列表,扫描 `.worktrees/worktree-<branch
|
|
100
|
+
脚本自动从 `workspace-config.json` 读取应用列表,扫描 `.worktrees/worktree-<branch>/.codespace/<应用名>/`(兼容 ≤beta.23 旧平铺 `<应用名>/`)实际存在的子目录。
|
|
101
101
|
|
|
102
102
|
**解读末行 `DISCOVER_RESULT`**:
|
|
103
103
|
- `workspace` 字段:workspace worktree 绝对路径(AI agent 记录供后续命令使用)
|
|
104
104
|
- `apps` 数组:实际存在的应用 worktree 列表(每项含 `name`, `repo`, `side`, `wt_path`, `app_path`)
|
|
105
105
|
|
|
106
|
-
> 说明:app worktree 物理嵌套在 workspace worktree
|
|
106
|
+
> 说明:app worktree 物理嵌套在 workspace worktree 的 `.codespace/` 容器内,移除 workspace worktree 时嵌套的子目录会一并消失,但各 app 仓库的 git worktree 元数据(`.git/worktrees/`)仍需通过 `git worktree remove` 逐个清理。AI 配置经 junction 链入 worktree(只读内容子目录指向空间根唯一物理副本),随 worktree 目录一并消失,无需特殊处理。
|
|
107
107
|
|
|
108
108
|
### 1.3 检查未归档 change
|
|
109
109
|
|
|
@@ -393,8 +393,8 @@ git -C "<ws_root>" worktree list
|
|
|
393
393
|
当前目录:<工作空间根目录绝对路径>
|
|
394
394
|
已移除 worktree:
|
|
395
395
|
- .worktrees/worktree-feature-xxx(workspace)
|
|
396
|
-
- .worktrees/worktree-feature-xxx/ep-user-info-server(嵌套,ep-user-info-server 仓库)
|
|
397
|
-
- .worktrees/worktree-feature-xxx/ep-user-management-app(嵌套,ep-user-management-app 仓库)
|
|
396
|
+
- .worktrees/worktree-feature-xxx/.codespace/ep-user-info-server(嵌套,ep-user-info-server 仓库)
|
|
397
|
+
- .worktrees/worktree-feature-xxx/.codespace/ep-user-management-app(嵌套,ep-user-management-app 仓库)
|
|
398
398
|
未提交修改:已丢弃(--force)/ 无
|
|
399
399
|
未归档 change 处理:
|
|
400
400
|
✅ ai-call-send-im-msg → 已归档到 archive/2026-07-08-ai-call-send-im-msg
|
|
@@ -430,7 +430,7 @@ git -C "<ws_root>" worktree list
|
|
|
430
430
|
|
|
431
431
|
### 必须执行
|
|
432
432
|
|
|
433
|
-
- 读 `workspace-config.json` 取应用名列表;以 `.worktrees/worktree
|
|
433
|
+
- 读 `workspace-config.json` 取应用名列表;以 `.worktrees/worktree-<需求名>/.codespace/<应用名>/` 实际存在性确定关联清单
|
|
434
434
|
- 移除/删除前执行安全评估(未提交修改 / 是否合并 / 是否推远程)并报告
|
|
435
435
|
- 严格按 Step 4.2(移除 worktree)→ Step 4.4(删分支)顺序
|
|
436
436
|
- 移除 workspace worktree 前先把会话移出(`cd "<WS_ROOT>" &&` 或 `git -C` 绝对路径)
|
|
@@ -50,9 +50,18 @@ if (!fs.existsSync(wtDir)) {
|
|
|
50
50
|
process.exit(2);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/** 解析 app worktree 物理路径:新布局 .codespace/<app> 优先,兼容 ≤beta.23 旧平铺 <app> */
|
|
54
|
+
function resolveAppWt(appName) {
|
|
55
|
+
const nested = path.join(wtDir, '.codespace', appName);
|
|
56
|
+
if (fs.existsSync(path.join(nested, '.git'))) return nested;
|
|
57
|
+
const legacy = path.join(wtDir, appName);
|
|
58
|
+
if (fs.existsSync(path.join(legacy, '.git'))) return legacy;
|
|
59
|
+
return nested;
|
|
60
|
+
}
|
|
61
|
+
|
|
53
62
|
const found = [];
|
|
54
63
|
for (const app of apps) {
|
|
55
|
-
const appWt =
|
|
64
|
+
const appWt = resolveAppWt(app.name);
|
|
56
65
|
if (fs.existsSync(appWt) && fs.existsSync(path.join(appWt, '.git'))) {
|
|
57
66
|
console.log('✅ ' + app.name + ':存在嵌套 worktree(' + appWt + ')');
|
|
58
67
|
found.push({
|
|
@@ -88,9 +88,18 @@ let config = {};
|
|
|
88
88
|
try { config = JSON.parse(fs.readFileSync(configPath, 'utf-8')); } catch (e) {}
|
|
89
89
|
|
|
90
90
|
const apps = config.apps || [];
|
|
91
|
+
/** 解析 app worktree 物理路径:新布局 .codespace/<app> 优先,兼容 ≤beta.23 旧平铺 <app> */
|
|
92
|
+
function resolveAppWt(appName) {
|
|
93
|
+
const nested = path.join(wtDir, '.codespace', appName);
|
|
94
|
+
if (fs.existsSync(path.join(nested, '.git'))) return nested;
|
|
95
|
+
const legacy = path.join(wtDir, appName);
|
|
96
|
+
if (fs.existsSync(path.join(legacy, '.git'))) return legacy;
|
|
97
|
+
return nested;
|
|
98
|
+
}
|
|
99
|
+
|
|
91
100
|
console.log('=== 移除应用 Worktrees ===');
|
|
92
101
|
for (const app of apps) {
|
|
93
|
-
const appWt =
|
|
102
|
+
const appWt = resolveAppWt(app.name);
|
|
94
103
|
const appPath = path.join(wsRoot, '.codespace', app.name);
|
|
95
104
|
removeWt(appPath, appWt, app.name);
|
|
96
105
|
}
|
|
@@ -159,12 +159,21 @@ try { config = JSON.parse(fs.readFileSync(configPath, 'utf-8')); } catch (e) {}
|
|
|
159
159
|
const apps = config.apps || [];
|
|
160
160
|
const wtDir = path.join(wsRoot, '.worktrees', 'worktree-' + branch);
|
|
161
161
|
|
|
162
|
+
/** 解析 app worktree 物理路径:新布局 .codespace/<app> 优先,兼容 ≤beta.23 旧平铺 <app> */
|
|
163
|
+
function resolveAppWt(appName) {
|
|
164
|
+
const nested = path.join(wtDir, '.codespace', appName);
|
|
165
|
+
if (fs.existsSync(path.join(nested, '.git'))) return nested;
|
|
166
|
+
const legacy = path.join(wtDir, appName);
|
|
167
|
+
if (fs.existsSync(path.join(legacy, '.git'))) return legacy;
|
|
168
|
+
return nested;
|
|
169
|
+
}
|
|
170
|
+
|
|
162
171
|
const results = [];
|
|
163
172
|
|
|
164
173
|
results.push(checkRepo('workspace', wsRoot, wtDir));
|
|
165
174
|
|
|
166
175
|
for (const app of apps) {
|
|
167
|
-
const appWt =
|
|
176
|
+
const appWt = resolveAppWt(app.name);
|
|
168
177
|
const appPath = path.join(wsRoot, '.codespace', app.name);
|
|
169
178
|
results.push(checkRepo(app.name, appPath, appWt));
|
|
170
179
|
}
|
|
@@ -12,7 +12,7 @@ description: "为一个需求在 workspace 与各关联应用仓库同步创建
|
|
|
12
12
|
## 核心原则
|
|
13
13
|
|
|
14
14
|
- 本技能的所有原子操作封装为 `scripts/*.js` Node.js 脚本,**跨平台执行**(Windows PowerShell / macOS bash / Linux bash / Git Bash 均无需适配)
|
|
15
|
-
-
|
|
15
|
+
- 物理位置(ADR 0003):workspace worktree 在 `.worktrees/worktree-<需求名>/`,各 app worktree 收拢在其 `.codespace/<应用名>/` 容器内——**镜像空间根布局**(应用检出恒在 `.codespace/<app>/`,相对路径两模式通用)
|
|
16
16
|
- 所有仓库使用统一的分支名(不加前缀);worktree 目录名自动加 `worktree-` 前缀;**分支基线统一取 `origin/<main>`(ADR 0002,不取 HEAD)**
|
|
17
17
|
- **lazy push(ADR 0002)**:本技能**不向远端推送任何分支**——远端副作用收敛到 `cbb-worktree-push` 唯一出口;推送失败早暴露的职责由 push 技能的 dry-run 承担
|
|
18
18
|
- **AI 配置 junction(ADR 0002 防线 B)**:创建 worktree 后把根的 AI 配置**内容子目录**(rules / skills / commands)junction 进工作树——开发会话开在 `<wt_path>`(上游 `/opsx:*` 相对路径原样生效,零翻译);工具状态落在工作树自建空壳目录,不互相污染;`cbb update` 在根跑一次全 worktree 生效
|
|
@@ -280,7 +280,7 @@ node "<skill_dir>/scripts/create-worktrees.js" "<ws_root>" "<branch>" [--apps "a
|
|
|
280
280
|
脚本内部自动完成:
|
|
281
281
|
1. **元数据自愈**:仅 `git worktree prune`(清理"目录已缺失/失效"的注册项);**绝不删除任何物理目录**(0.0.3-beta.11 起——旧版"清理无元数据残留目录"因 Windows 路径分隔符不匹配会误删活跃 worktree,已整体移除)
|
|
282
282
|
2. **workspace worktree 创建**:`git worktree add .worktrees/worktree-<branch> <branch>`
|
|
283
|
-
3. **app worktree
|
|
283
|
+
3. **app worktree 创建**(收拢在 workspace worktree 的 `.codespace/` 容器内,镜像空间根布局);≤beta.23 旧平铺布局自动 `git worktree move` 迁移
|
|
284
284
|
|
|
285
285
|
**解读输出**:
|
|
286
286
|
- 退出码 0 + 末行 `WT_PATH <绝对路径>` → 全部成功,AI 解析出 worktree 绝对路径供后续 Step 4 使用
|
|
@@ -333,7 +333,7 @@ node "<skill_dir>/scripts/junction-ai-config.js" "<ws_root>" "<wt_path>"
|
|
|
333
333
|
|
|
334
334
|
```
|
|
335
335
|
✅ worktree 已创建:<wt_path>
|
|
336
|
-
关联应用:<应用名 →
|
|
336
|
+
关联应用:<应用名 → .codespace/<应用名> 路径> 列表(应用子集或全量)
|
|
337
337
|
🔗 AI 配置已 junction 就位:rules / skills / commands(cbb update 一处生效)
|
|
338
338
|
📌 开发会话请开在 <wt_path>(新开 IDE 窗口 / agent 会话);本根会话保留作控制面
|
|
339
339
|
👉 下一步建议:
|
|
@@ -354,7 +354,7 @@ node "<skill_dir>/scripts/junction-ai-config.js" "<ws_root>" "<wt_path>"
|
|
|
354
354
|
|
|
355
355
|
| 场景 | 重跑行为 |
|
|
356
356
|
|------|----------|
|
|
357
|
-
| 空间新增了应用(编辑 `workspace-config.json` 后) | Step 1 拉取新应用代码 → Step 3.3 为新应用建分支 → Step 3.5
|
|
357
|
+
| 空间新增了应用(编辑 `workspace-config.json` 后) | Step 1 拉取新应用代码 → Step 3.3 为新应用建分支 → Step 3.5 在 `.codespace/` 容器内创建缺失的 app worktree → Step 3.6 补 junction |
|
|
358
358
|
| 上次 init 中途失败 | 已存在的分支 / worktree 自动复用,`.gitignore` 处理自动跳过,仅补齐缺失部分 |
|
|
359
359
|
| 应用从 config 移除 | 已创建的分支 / worktree **不会**自动删除;如需清理用 `cbb-worktree-close` skill 关闭后重建 |
|
|
360
360
|
| **本地模式升级 worktree**(ADR 0002) | Step 3.3 分支已存在 → reused(本地阶段提交随身带走);先 `git checkout main` 腾出各仓库本地检出,再重跑本技能 |
|
|
@@ -109,12 +109,13 @@ if (fs.existsSync(wtDir)) {
|
|
|
109
109
|
console.log('✅ workspace worktree 已创建:' + wtDir);
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
// 3. apps worktree
|
|
112
|
+
// 3. apps worktree 创建(物理嵌套于 workspace worktree 的 .codespace/ 容器内,镜像主布局,ADR 0003)
|
|
113
113
|
console.log('=== 创建 App Worktrees ===');
|
|
114
114
|
const results = [];
|
|
115
115
|
for (const app of apps) {
|
|
116
116
|
const appPath = path.join(wsRoot, '.codespace', app.name);
|
|
117
|
-
const nestedWt = path.join(wtDir, app.name);
|
|
117
|
+
const nestedWt = path.join(wtDir, '.codespace', app.name);
|
|
118
|
+
const legacyWt = path.join(wtDir, app.name);
|
|
118
119
|
|
|
119
120
|
if (fs.existsSync(nestedWt) && fs.existsSync(path.join(nestedWt, '.git'))) {
|
|
120
121
|
console.log('⏭️ ' + app.name + ': 嵌套 worktree 已存在,复用');
|
|
@@ -122,8 +123,23 @@ for (const app of apps) {
|
|
|
122
123
|
continue;
|
|
123
124
|
}
|
|
124
125
|
|
|
125
|
-
//
|
|
126
|
+
// 防御性兜底:容器目录不存在则创建
|
|
126
127
|
fs.mkdirSync(path.dirname(nestedWt), { recursive: true });
|
|
128
|
+
|
|
129
|
+
// 存量迁移:≤beta.23 旧平铺布局 → .codespace/ 容器(worktree 平移,分支与提交随身带走)
|
|
130
|
+
if (fs.existsSync(legacyWt) && fs.existsSync(path.join(legacyWt, '.git'))) {
|
|
131
|
+
const moved = run('git worktree move "' + legacyWt + '" "' + nestedWt + '"', appPath);
|
|
132
|
+
if (moved.ok) {
|
|
133
|
+
console.log('🔁 ' + app.name + ': 旧平铺 worktree 已迁移到 .codespace/ 容器');
|
|
134
|
+
results.push({ name: app.name, status: 'migrated' });
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
console.error('❌ ' + app.name + ': 旧平铺 worktree 迁移失败(常见原因:目录被 IDE 等进程占用):' + moved.err);
|
|
138
|
+
console.error(' 请关闭占用 ' + legacyWt + ' 的程序后重跑本技能;或先执行 cbb-worktree-close 再重建');
|
|
139
|
+
results.push({ name: app.name, status: 'failed', error: moved.err });
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
|
|
127
143
|
const created = run('git worktree add "' + nestedWt + '" "' + branch + '"', appPath);
|
|
128
144
|
if (created.ok) {
|
|
129
145
|
console.log('✅ ' + app.name + ': 嵌套 worktree 创建成功');
|
|
@@ -8,7 +8,7 @@ require('./silence-popup');
|
|
|
8
8
|
*
|
|
9
9
|
* 行为:
|
|
10
10
|
* 1. 按需求名发现参与仓库(ADR 0002 双布局):
|
|
11
|
-
* - worktree 布局:.worktrees/worktree-<branch>/
|
|
11
|
+
* - worktree 布局:.worktrees/worktree-<branch>/ 的 workspace + 其 .codespace/<app>/ 容器内各 app worktree(镜像主布局,ADR 0003)
|
|
12
12
|
* - 本地布局:根 / .codespace/<app>/ 检出恰在 <branch> 分支上
|
|
13
13
|
* 2. 对每个仓库:
|
|
14
14
|
* a. git status --porcelain 检测 dirty
|
|
@@ -97,6 +97,16 @@ function isGitRepo(p) {
|
|
|
97
97
|
return fs.existsSync(path.join(p, '.git'));
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
/** 读取 workspace-config.json 的 app 名列表(旧布局兼容判定用;缺失返回 []) */
|
|
101
|
+
function loadAppNames(wsRoot) {
|
|
102
|
+
try {
|
|
103
|
+
const cfg = JSON.parse(fs.readFileSync(path.join(wsRoot, 'workspace-config.json'), 'utf-8'));
|
|
104
|
+
return Array.isArray(cfg.apps) ? cfg.apps.map(a => a.name).filter(Boolean) : [];
|
|
105
|
+
} catch (e) {
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
100
110
|
function currentBranch(p) {
|
|
101
111
|
try {
|
|
102
112
|
return execSync('git branch --show-current', { cwd: p, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] }).toString().trim();
|
|
@@ -105,7 +115,7 @@ function currentBranch(p) {
|
|
|
105
115
|
|
|
106
116
|
/**
|
|
107
117
|
* 按需求名发现参与仓库(ADR 0002 双布局):
|
|
108
|
-
* 布局一 worktree:.worktrees/worktree-<branch>/
|
|
118
|
+
* 布局一 worktree:.worktrees/worktree-<branch>/ 的 workspace + 其 .codespace/<app>/ 容器内 app worktree(ADR 0003 镜像主布局;≤beta.23 旧平铺顶层布局按配置 app 名兼容)
|
|
109
119
|
* 布局二 本地模式:根 / .codespace/<app>/ 的检出恰在 <branch> 分支上(同名仓库以 worktree 布局优先)
|
|
110
120
|
* 两者皆空 → error。
|
|
111
121
|
*/
|
|
@@ -118,12 +128,24 @@ function scanWorktreeRepos(wsRoot, branch) {
|
|
|
118
128
|
if (isGitRepo(wtRoot)) {
|
|
119
129
|
repos.push({ name: 'workspace', wt: wtRoot, repo: wsRoot, layout: 'worktree' });
|
|
120
130
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
131
|
+
// app worktree 收拢在 .codespace/ 容器内(镜像主布局,ADR 0003)
|
|
132
|
+
const appContainer = path.join(wtRoot, '.codespace');
|
|
133
|
+
if (fs.existsSync(appContainer)) {
|
|
134
|
+
for (const e of fs.readdirSync(appContainer, { withFileTypes: true })) {
|
|
135
|
+
if (!e.isDirectory()) continue;
|
|
136
|
+
const appWt = path.join(appContainer, e.name);
|
|
137
|
+
if (!isGitRepo(appWt)) continue;
|
|
138
|
+
repos.push({ name: e.name, wt: appWt, repo: path.join(wsRoot, '.codespace', e.name), layout: 'worktree' });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// 兼容 ≤beta.23 旧平铺布局:按配置 app 名精确匹配(避免把根仓库自带的嵌套 git 目录误判为 app)
|
|
142
|
+
for (const name of loadAppNames(wsRoot)) {
|
|
143
|
+
if (repos.some(r => r.name === name)) continue;
|
|
144
|
+
const legacyWt = path.join(wtRoot, name);
|
|
145
|
+
if (fs.existsSync(path.join(legacyWt, '.git'))) {
|
|
146
|
+
console.error('⚠️ ' + name + ' 仍是旧平铺布局(' + legacyWt + '),建议重跑 cbb-worktree-init 迁移到 .codespace/ 容器');
|
|
147
|
+
repos.push({ name, wt: legacyWt, repo: path.join(wsRoot, '.codespace', name), layout: 'worktree' });
|
|
148
|
+
}
|
|
127
149
|
}
|
|
128
150
|
}
|
|
129
151
|
|
|
@@ -8,7 +8,7 @@ require('./silence-popup');
|
|
|
8
8
|
*
|
|
9
9
|
* 行为:
|
|
10
10
|
* 1. 按需求名发现参与仓库(ADR 0002 双布局):
|
|
11
|
-
* - worktree 布局:.worktrees/worktree-<branch>/
|
|
11
|
+
* - worktree 布局:.worktrees/worktree-<branch>/ 的 workspace + 其 .codespace/<app>/ 容器内各 app worktree(镜像主布局,ADR 0003)
|
|
12
12
|
* - 本地布局:根 / .codespace/<app>/ 检出恰在 <branch> 分支上
|
|
13
13
|
* 2. 对每个仓库调 push-core.pushSingle(已建立 upstream 时直接 push -u;否则建立 upstream)
|
|
14
14
|
* 3. 逐个报告状态,输出末行 PUSH_RESULT
|
|
@@ -59,6 +59,16 @@ function isGitRepo(p) {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
/** 读取 workspace-config.json 的 app 名列表(旧布局兼容判定用;缺失返回 []) */
|
|
63
|
+
function loadAppNames(wsRoot) {
|
|
64
|
+
try {
|
|
65
|
+
const cfg = JSON.parse(fs.readFileSync(path.join(wsRoot, 'workspace-config.json'), 'utf-8'));
|
|
66
|
+
return Array.isArray(cfg.apps) ? cfg.apps.map(a => a.name).filter(Boolean) : [];
|
|
67
|
+
} catch (e) {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
62
72
|
function currentBranch(p) {
|
|
63
73
|
try {
|
|
64
74
|
return execSync('git branch --show-current', { cwd: p, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] }).toString().trim();
|
|
@@ -67,7 +77,7 @@ function currentBranch(p) {
|
|
|
67
77
|
|
|
68
78
|
/**
|
|
69
79
|
* 按需求名发现参与仓库(ADR 0002 双布局):
|
|
70
|
-
* 布局一 worktree:.worktrees/worktree-<branch>/
|
|
80
|
+
* 布局一 worktree:.worktrees/worktree-<branch>/ 的 workspace + 其 .codespace/<app>/ 容器内 app worktree(ADR 0003 镜像主布局;≤beta.23 旧平铺顶层布局按配置 app 名兼容)
|
|
71
81
|
* 布局二 本地模式:根 / .codespace/<app>/ 的检出恰在 <branch> 分支上(同名仓库以 worktree 布局优先)
|
|
72
82
|
* 两者皆空 → error。
|
|
73
83
|
*/
|
|
@@ -80,12 +90,24 @@ function scanWorktreeRepos(wsRoot, branch) {
|
|
|
80
90
|
if (isGitRepo(wtRoot)) {
|
|
81
91
|
repos.push({ name: 'workspace', wt: wtRoot, repo: wsRoot, layout: 'worktree' });
|
|
82
92
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
93
|
+
// app worktree 收拢在 .codespace/ 容器内(镜像主布局,ADR 0003)
|
|
94
|
+
const appContainer = path.join(wtRoot, '.codespace');
|
|
95
|
+
if (fs.existsSync(appContainer)) {
|
|
96
|
+
for (const e of fs.readdirSync(appContainer, { withFileTypes: true })) {
|
|
97
|
+
if (!e.isDirectory()) continue;
|
|
98
|
+
const appWt = path.join(appContainer, e.name);
|
|
99
|
+
if (!isGitRepo(appWt)) continue;
|
|
100
|
+
repos.push({ name: e.name, wt: appWt, repo: path.join(wsRoot, '.codespace', e.name), layout: 'worktree' });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// 兼容 ≤beta.23 旧平铺布局:按配置 app 名精确匹配(避免把根仓库自带的嵌套 git 目录误判为 app)
|
|
104
|
+
for (const name of loadAppNames(wsRoot)) {
|
|
105
|
+
if (repos.some(r => r.name === name)) continue;
|
|
106
|
+
const legacyWt = path.join(wtRoot, name);
|
|
107
|
+
if (fs.existsSync(path.join(legacyWt, '.git'))) {
|
|
108
|
+
console.error('⚠️ ' + name + ' 仍是旧平铺布局(' + legacyWt + '),建议重跑 cbb-worktree-init 迁移到 .codespace/ 容器');
|
|
109
|
+
repos.push({ name, wt: legacyWt, repo: path.join(wsRoot, '.codespace', name), layout: 'worktree' });
|
|
110
|
+
}
|
|
89
111
|
}
|
|
90
112
|
}
|
|
91
113
|
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|------|----------|----------|
|
|
20
20
|
| `workspace-config.json` | 关联应用清单,所有应用联动的唯一数据源 | 人工编辑(控制面白名单,可直接提交 main) |
|
|
21
21
|
| `.codespace/` | 各应用的**标准本地检出**(基准 = `origin/main`) | `cbb setup` 与技能自动 clone / fetch;本地模式可在其中**切需求分支开发**(main 检出禁改);已 gitignore,不提交 |
|
|
22
|
-
| `.worktrees/` | 需求隔离开发区(每个需求一个 `worktree-<需求名>/`,内含 AI 配置 junction
|
|
22
|
+
| `.worktrees/` | 需求隔离开发区(每个需求一个 `worktree-<需求名>/`,内含 AI 配置 junction 与 `.codespace/<app>/` 应用检出,**镜像本空间布局**) | worktree 技能自动创建 / 清理;已 gitignore,不提交 |
|
|
23
23
|
| `openspec/` | OpenSpec 工作流配置(`config.yaml` + `schemas/`) | `config.yaml`/`schemas/` 为控制面白名单(`cbb update` 部署,可直改);变更产物(`openspec/changes/`)落需求分支随 MR 合入 |
|
|
24
24
|
| `.claude/` `.qoder/` `.opencode/` `.workbuddy/` `.trae/` | AI 工具适配目录:编码规范规则、OpenSpec 命令、技能 | 由 cbb 按 setup 时选择的工具安装(**唯一物理副本在此**);worktree 内经 junction 引用同一份;已 gitignore,不提交,勿手动改 |
|
|
25
25
|
| `.cbb/` | cbb 安装清单与状态(`.managed-by-cbb`、`.last-tools`) | 由 cbb 管理;**勿手动编辑** |
|