@jspg-ai/coding-bb 0.0.5-beta.5 → 0.0.5-beta.6
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-workspace-discipline.md +1 -1
- package/cbb/worktrees/skills/cbb-worktree-init/SKILL.md +27 -23
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/check-repos.js +21 -22
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/create-worktrees.js +8 -0
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/update-gitignore.js +62 -18
- package/package.json +1 -1
|
@@ -16,7 +16,7 @@ trigger: always_on
|
|
|
16
16
|
- **严禁直接在任何主干检出上提交载荷**:分支感知 pre-commit hook 机械拦截(workspace 仓库仅放行控制面白名单:`CBB-WORKSPACE.json` / `.gitignore` / `AGENTS.md` / `openspec/config.yaml` / `openspec/schemas/**`;应用仓库主干全拦;**merge 提交(`MERGE_HEAD` 存在)放行**,见 ADR 0007)——**被拦是防线生效,不是故障**;`--no-verify` 绕过仅限用户显式要求
|
|
17
17
|
- 需求分支推送唯一出口 = `cbb-worktree-push`(init 创建流程零远端操作,lazy push);推送后可选提 MR 或**直接合并**(`merge-branches.js`,主干 / 测试分支,ADR 0008)
|
|
18
18
|
- **收尾合并门禁(ADR 0007)**:两模式收尾均须每仓库"已合并"(`safety-check` / `merge-branches.js --check` 判定);未合并三选一——提 MR / 直合并 / 确认废弃(远程重命名 `<分支>-deprecated`);交付完成回主干并自动删本地+远程分支,**废弃出口保留本地分支**(细则见 cbb-local-mode Step 5 / cbb-worktree-close Step 3.4)
|
|
19
|
-
- **模式误判零代价**:本地模式中途要并行/隔离 → 重跑 `cbb-worktree-init
|
|
19
|
+
- **模式误判零代价**:本地模式中途要并行/隔离 → 重跑 `cbb-worktree-init`——并行新需求直接建(不占本地检出、互不干扰);升级当前需求先回主干腾检出后重跑(分支自动复用、提交随身带走)
|
|
20
20
|
|
|
21
21
|
**五时刻对齐(agent 必须遵守;1–3 的细则见 `cbb-local-mode` 技能):**
|
|
22
22
|
|
|
@@ -129,12 +129,12 @@ worktree 需要知道要关联哪些应用仓库。
|
|
|
129
129
|
]
|
|
130
130
|
}
|
|
131
131
|
```
|
|
132
|
-
-
|
|
132
|
+
- 写入后提交到当前分支(Step 2 状态检查与根检出分支无关,本地模式进行中直接提交即可):
|
|
133
133
|
```bash
|
|
134
134
|
git add CBB-WORKSPACE.json
|
|
135
135
|
git commit -m "chore: add CBB-WORKSPACE.json"
|
|
136
136
|
```
|
|
137
|
-
>
|
|
137
|
+
> 提交随所在分支的生命周期进入主干(在需求分支上 = 随该需求合并;在主干上 = 直接生效);无需为此切换分支,控制面提交的推送按团队常规流程处理。
|
|
138
138
|
- 提交后重新从 Step 0 校验,通过后继续 Step 1
|
|
139
139
|
|
|
140
140
|
> ⚠️ **禁止**:在 CBB-WORKSPACE.json 无效时跳过本步骤直接创建 worktree,否则无法确定关联应用。
|
|
@@ -181,17 +181,18 @@ node "<skill_dir>/scripts/sync-repos.js" "<ws_root>"
|
|
|
181
181
|
|
|
182
182
|
## Step 2:状态检查
|
|
183
183
|
|
|
184
|
-
|
|
184
|
+
对工作空间和关联应用仓库执行**统一轻量检查**——路径/仓库形态/`git fetch`/`origin/main` 引用可用,workspace 另加 HEAD 非 detached(update-gitignore 的回切依赖分支名)。检查与根检出分支状态解耦(分支基线恒为 `origin/<main>`,本地模式进行中根检出在任何分支均可通过;`.gitignore` 操作由 Step 3.2 的 stash 机制保护)。**全部通过才继续,任一失败则终止并报告所有问题**(不 fail-fast,跑完所有仓库再汇总)。
|
|
185
185
|
|
|
186
186
|
| 检查项 | workspace 仓库 | app 仓库 |
|
|
187
187
|
|--------|:---:|:---:|
|
|
188
188
|
| 路径存在且是 git 仓库 | ✅ | ✅ |
|
|
189
189
|
| 不在 worktree 中(排除 submodule) | ✅ | ✅ |
|
|
190
190
|
| 不是 submodule | ✅ | ✅ |
|
|
191
|
+
| HEAD 非 detached | ✅ | ❌(无 checkout 动作) |
|
|
191
192
|
| `git fetch` 拉取远程最新 | ✅ | ✅ |
|
|
192
|
-
|
|
|
193
|
+
| `origin/main` 引用可用 | ✅ | ✅ |
|
|
193
194
|
| 无未提交修改 | ❌(由 Step 3.2 stash 处理) | ❌ |
|
|
194
|
-
| 当前在主分支上 |
|
|
195
|
+
| 当前在主分支上 | ❌(本地模式进行中仍可创建) | ❌ |
|
|
195
196
|
|
|
196
197
|
```bash
|
|
197
198
|
node "<skill_dir>/scripts/check-repos.js" "<ws_root>"
|
|
@@ -199,7 +200,7 @@ node "<skill_dir>/scripts/check-repos.js" "<ws_root>"
|
|
|
199
200
|
|
|
200
201
|
**解读输出**:
|
|
201
202
|
- 退出码 0 + 末行 `CHECK_RESULT ALL_OK` → 全部通过,继续 Step 3
|
|
202
|
-
- 退出码 2 + 末行 `CHECK_RESULT HAS_FAILURES` → 部分失败,AI agent
|
|
203
|
+
- 退出码 2 + 末行 `CHECK_RESULT HAS_FAILURES` → 部分失败,AI agent 报告上面列出的所有问题,**终止流程**(若报 `detached HEAD` → 提示用户先 `git checkout <分支>` 挂回某分支再重跑)
|
|
203
204
|
|
|
204
205
|
---
|
|
205
206
|
|
|
@@ -228,15 +229,17 @@ node "<skill_dir>/scripts/check-repos.js" "<ws_root>"
|
|
|
228
229
|
node "<skill_dir>/scripts/update-gitignore.js" "<ws_root>" "<branch>"
|
|
229
230
|
```
|
|
230
231
|
|
|
231
|
-
|
|
232
|
+
脚本内部自动完成:同名升级早退 → ensure 本地 `.git/info/exclude` 兜底(在 stash 前)→ stash 保护(`-u` 含 untracked)→ 分支不存在则从 `<remote>/<main>` 创建 → 切分支 → 添加 `.worktrees/` 和 `.codespace/` → 提交 → 切回原分支 → pop stash
|
|
232
233
|
|
|
233
234
|
**解读输出**:
|
|
234
235
|
- 退出码 0 + 末行 `GITIGNORE_OK` → 成功
|
|
235
|
-
- 退出码 2 →
|
|
236
|
+
- 退出码 2 → 执行失败,**终止流程**(含**同名升级早退**:目标分支正被根检出占用——本地模式同名升级 worktree 场景,零副作用退出并提示先收尾本地模式或把根检出切回主干,主干名以远端 HEAD 为准、勿写死 main)
|
|
236
237
|
|
|
237
|
-
> ⓘ **分支自动创建**:本步骤先于 Step 3.3 执行,workspace 分支此时不存在,脚本会自动从
|
|
238
|
+
> ⓘ **分支自动创建**:本步骤先于 Step 3.3 执行,workspace 分支此时不存在,脚本会自动从 `<remote>/<main>` 创建(与 `create-branches.js` 对 workspace 的行为一致,Step 3.3 报 `reused`)。基线不取 HEAD——本地模式进行中重跑本技能,根检出在任何分支/有未提交修改均不被污染。`.gitignore` 提交落在需求分支上,由后续 `cbb-worktree-push` 首次推送一并带入。
|
|
238
239
|
|
|
239
|
-
>
|
|
240
|
+
> ⓘ **info/exclude 本地兜底**:脚本在 stash **之前**把 `.worktrees/`、`.codespace/` 幂等写入本地 `.git/info/exclude`——先忽略两个目录,`stash -u` 才不会把 `.codespace/`(应用全量检出)一并吞入;同时 gitignore 条目在需求分支合并前,根检出(其他分支)仍缺条目、目录显示 untracked(存量空间噪音),exclude 与分支无关、即时消噪。团队层条目仍走 .gitignore 提交(检测按文件内容,不受 exclude 干扰)。
|
|
241
|
+
|
|
242
|
+
> ⚠️ 必须在提交后切回**原检出分支**(脚本已自动处理,含本地模式的需求分支),否则 Step 3.5 的 `git worktree add` 会因目标分支仍被根检出占用而失败。
|
|
240
243
|
|
|
241
244
|
### 3.3 创建分支
|
|
242
245
|
|
|
@@ -367,8 +370,8 @@ node "<skill_dir>/scripts/junction-ai-config.js" "<ws_root>" "<wt_path>"
|
|
|
367
370
|
| 空间新增了应用(编辑 `CBB-WORKSPACE.json` 后) | Step 1 拉取新应用代码 → Step 3.3 为新应用建分支 → Step 3.5 在 `.codespace/` 容器内创建缺失的 app worktree → Step 3.6 补 junction |
|
|
368
371
|
| 上次 init 中途失败 | 已存在的分支 / worktree 自动复用,`.gitignore` 处理自动跳过,仅补齐缺失部分 |
|
|
369
372
|
| 应用从 config 移除 | 已创建的分支 / worktree **不会**自动删除;如需清理用 `cbb-worktree-close` skill 关闭后重建 |
|
|
370
|
-
| **本地模式升级 worktree**(ADR 0002) | Step 3.
|
|
371
|
-
| 本地模式进行中重跑 | 安全:分支基线取 `origin/main`(非 HEAD),不受根/应用检出状态影响;fetch
|
|
373
|
+
| **本地模式升级 worktree**(ADR 0002) | Step 3.2 检测到根检出即目标分支 → **零副作用早退**并提示先把各仓库本地检出切回主干(主干名以远端 HEAD 为准,勿写死 main)后重跑;切回后重跑:Step 3.3 分支已存在 → reused(本地阶段提交随身带走) |
|
|
374
|
+
| 本地模式进行中重跑 | 安全:分支基线取 `origin/main`(非 HEAD),不受根/应用检出状态影响;fetch 不动工作区;根检出在任何分支均可通过 Step 2 |
|
|
372
375
|
|
|
373
376
|
> ⓘ 本技能**零远端副作用**(lazy push):不存在"重跑推送开发中提交"的风险面——推送只发生在 `cbb-worktree-push`。
|
|
374
377
|
|
|
@@ -378,9 +381,9 @@ node "<skill_dir>/scripts/junction-ai-config.js" "<ws_root>" "<wt_path>"
|
|
|
378
381
|
|
|
379
382
|
| 场景 | 处理方式 |
|
|
380
383
|
|------|----------|
|
|
381
|
-
| `.worktrees/` / `.codespace/` 未在 `.gitignore` | `cbb setup` / `cbb update` 已写入(0.0.3-beta.22 起含 `.worktrees/`);存量空间由 Step 3.2
|
|
384
|
+
| `.worktrees/` / `.codespace/` 未在 `.gitignore` | `cbb setup` / `cbb update` 已写入(0.0.3-beta.22 起含 `.worktrees/`);存量空间由 Step 3.2 补写提交(同时幂等写入本地 `.git/info/exclude` 兜底消噪) |
|
|
382
385
|
| `git worktree add` 权限被拒 | 运行环境降级,中断并告知用户 |
|
|
383
|
-
| 状态检查任一失败 |
|
|
386
|
+
| 状态检查任一失败 | 终止并报告所有问题(`detached HEAD` → 提示用户先 `git checkout <分支>`) |
|
|
384
387
|
| 部分应用 worktree 创建失败 | 提示已创建的回滚清理命令 |
|
|
385
388
|
| workspace 主分支 tracked 内容(openspec/、cbb/ 等) | 自动 checkout 到工作树,无需额外动作(cp/clone 都是多余的) |
|
|
386
389
|
| AI 配置目录(.claude/、.qoder/、.opencode/ 等) | Step 3.6 junction 内容子目录进工作树;唯一物理副本在空间根,`cbb update` 一处生效 |
|
|
@@ -400,10 +403,10 @@ node "<skill_dir>/scripts/junction-ai-config.js" "<ws_root>" "<wt_path>"
|
|
|
400
403
|
- **问题:** 直接在代码中写死仓库 URL 或本地路径,不同团队的仓库地址不同
|
|
401
404
|
- **修复:** 始终从 `CBB-WORKSPACE.json` 读取应用与远程仓库的映射;配置缺失/为空时按 Step 0.3 配置引导处理,不支持在流程中临时指定配置外的仓库
|
|
402
405
|
|
|
403
|
-
###
|
|
406
|
+
### 状态检查残留旧基线约束(要求工作区在主干 / 主分支已最新)
|
|
404
407
|
|
|
405
|
-
- **问题:**
|
|
406
|
-
- **修复:** Step 2
|
|
408
|
+
- **问题:** 分支基线取 HEAD 的时代要求「当前在主分支」「主分支已最新」,否则拦截;但自 ADR 0002 起所有仓库分支统一从 `origin/main` 创建,与本地检出状态完全解耦——本地模式进行中(根检出在需求分支)被误拦,且「本地 main 已最新」实际检测的是 HEAD 落后(语义错误)
|
|
409
|
+
- **修复:** Step 2 对 workspace 与 app 统一轻量检查(路径 / 仓库形态 / fetch + `origin/main` 引用可用;workspace 另加 HEAD 非 detached);同名升级场景(目标分支正被根检出占用)的强制点前移至 Step 3.2 零副作用早退——不再有「本地模式进行中不能创建 worktree」的误拦
|
|
407
410
|
|
|
408
411
|
### 状态检查失败后继续执行
|
|
409
412
|
|
|
@@ -413,17 +416,17 @@ node "<skill_dir>/scripts/junction-ai-config.js" "<ws_root>" "<wt_path>"
|
|
|
413
416
|
### 忽略 .gitignore 安全验证
|
|
414
417
|
|
|
415
418
|
- **问题:** `.worktrees/` 或 `.codespace/` 目录未被忽略,工作树或代码目录内容被 git 跟踪
|
|
416
|
-
- **修复:**
|
|
419
|
+
- **修复:** 创建工作树前必须验证两者已忽略——`.gitignore` 条目按文件内容检测并提交(团队层);同时幂等写入 `.git/info/exclude`(本地跨分支兜底,消除根检出在条目合并前的 untracked 噪音)
|
|
417
420
|
|
|
418
421
|
### 未先创建分支就直接创建 worktree
|
|
419
422
|
|
|
420
423
|
- **问题:** 直接用 `git worktree add -b` 同时创建分支和 worktree,导致部分仓库分支创建成功但 worktree 失败时状态不一致
|
|
421
|
-
- **修复:** 严格按 Step 3 先统一创建分支(3.3)、再统一创建 worktree(3.
|
|
424
|
+
- **修复:** 严格按 Step 3 先统一创建分支(3.3)、再统一创建 worktree(3.5)的顺序执行
|
|
422
425
|
|
|
423
426
|
### 用 checkout -b 创建分支后再创建 worktree
|
|
424
427
|
|
|
425
428
|
- **问题:** `git checkout -b <需求名>` 将主目录切到新分支,随后 `git worktree add .worktrees/worktree-<需求名> <需求名>` 报 `fatal: '<需求名>' is already checked out`
|
|
426
|
-
- **修复:** Step 3.3 只用 `git branch` 创建分支,不切换;分支检出由 Step 3.
|
|
429
|
+
- **修复:** Step 3.3 只用 `git branch` 创建分支,不切换;分支检出由 Step 3.5 的 worktree 完成
|
|
427
430
|
|
|
428
431
|
### 把 worktree 内容当作"软链/克隆"处理
|
|
429
432
|
|
|
@@ -474,7 +477,8 @@ node "<skill_dir>/scripts/junction-ai-config.js" "<ws_root>" "<wt_path>"
|
|
|
474
477
|
|
|
475
478
|
**必须执行**
|
|
476
479
|
|
|
477
|
-
- workspace
|
|
480
|
+
- workspace 与 app 仓库统一轻量检查(fetch + `origin/main` 引用可用即可;workspace 另加 HEAD 非 detached)——**不要求在主分支 / 本地 main 最新**:分支基线恒为 `origin/<main>`,本地模式进行中根检出在任何分支均可创建 worktree
|
|
481
|
+
- 同名升级场景强制点:目标分支正被根检出占用时,Step 3.2(update-gitignore)零副作用早退——禁止绕过早退强行创建
|
|
478
482
|
- 所有仓库分支统一从 `origin/<main>` 创建(`git branch <需求名> ${REMOTE}/${MAIN}`),不依赖本地检出状态(本地模式进行中重跑不受污染)
|
|
479
483
|
- 严格 Step 3.3 → Step 3.5 顺序(先统一创建分支,再统一创建 worktree;workspace 工作树先建,各应用按序补建)
|
|
480
484
|
- Step 3.6 junction AI 配置(失败不阻塞,但要报告)
|
|
@@ -490,8 +494,8 @@ node "<skill_dir>/scripts/junction-ai-config.js" "<ws_root>" "<wt_path>"
|
|
|
490
494
|
| `check-env.js` | 环境探活 | 无 | 0/1/2 |
|
|
491
495
|
| `parse-config.js` | 解析 CBB-WORKSPACE.json | 无 | 0/1/2 |
|
|
492
496
|
| `sync-repos.js` | 同步 .codespace/ 仓库(兜底 clone 时补装提交防线 hook) | ws_root | 0/1/2 |
|
|
493
|
-
| `check-repos.js` |
|
|
494
|
-
| `update-gitignore.js` | 安全更新 .gitignore | ws_root, branch | 0/1/2 |
|
|
497
|
+
| `check-repos.js` | 状态检查(统一轻量 + workspace detached 附加项) | ws_root | 0/1/2 |
|
|
498
|
+
| `update-gitignore.js` | 安全更新 .gitignore(同名升级早退 + info/exclude 兜底) | ws_root, branch | 0/1/2 |
|
|
495
499
|
| `create-branches.js` | 创建分支(统一从 origin/<main> 建) | ws_root, branch, [--apps] | 0/1/2 |
|
|
496
500
|
| `create-worktrees.js` | 创建 worktree(仅 prune 元数据,不删物理目录) | ws_root, branch, [--apps] | 0/1/2/3 |
|
|
497
501
|
| `junction-ai-config.js` | AI 配置内容子目录 junction 进工作树(ADR 0002 防线 B) | ws_root, wt_path | 0/1/2 |
|
|
@@ -6,17 +6,19 @@ require('./silence-popup');
|
|
|
6
6
|
* 用法:node check-repos.js <ws_root>
|
|
7
7
|
*
|
|
8
8
|
* 检查项:
|
|
9
|
-
* workspace
|
|
9
|
+
* workspace 仓库(轻量检查 + detached 附加项):
|
|
10
10
|
* \u2460 路径存在且是 git 仓库
|
|
11
11
|
* \u2461 不在 worktree 中(排除 submodule)
|
|
12
12
|
* \u2462 不是 submodule
|
|
13
|
-
* \u2463
|
|
14
|
-
* \u2464
|
|
13
|
+
* \u2463 HEAD 非 detached(仅 workspace;update-gitignore 回切依赖分支名)
|
|
14
|
+
* \u2464 git fetch + origin/main 引用可用
|
|
15
15
|
*
|
|
16
|
-
* app 仓库(轻量检查
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
16
|
+
* app 仓库(轻量检查 4 项):同 workspace 但无 detached 附加项
|
|
17
|
+
*
|
|
18
|
+
* 历史说明:workspace 曾要求「当前在主分支」「本地主分支已最新」——那是分支基线取
|
|
19
|
+
* HEAD 时代的约束;自 ADR 0002 起所有仓库分支统一从 origin/<main> 创建,与根检出
|
|
20
|
+
* 状态完全解耦(本地模式进行中根检出在任何分支均可创建 worktree),该两项检查已移除。
|
|
21
|
+
* 同名升级场景(目标分支正被根检出占用)的强制点移至 update-gitignore.js 的零副作用早退。
|
|
20
22
|
*
|
|
21
23
|
* 退出码:
|
|
22
24
|
* 0 - 全部通过
|
|
@@ -106,6 +108,16 @@ function checkRepo(repoPath, name, level) {
|
|
|
106
108
|
problems.push("is git submodule");
|
|
107
109
|
}
|
|
108
110
|
|
|
111
|
+
// 4.5: workspace 附加项 —— HEAD 必须挂在分支上(非 detached)。
|
|
112
|
+
// update-gitignore.js 的回切依赖分支名(origBranch),detached 下 origBranch 为
|
|
113
|
+
// "HEAD",回切会停到新分支 tip、丢失原位置。app 仓库无 checkout 动作,无需此项。
|
|
114
|
+
if (level === "workspace") {
|
|
115
|
+
const symRef = tryExec("git symbolic-ref -q HEAD", repoPath);
|
|
116
|
+
if (!symRef.ok) {
|
|
117
|
+
problems.push("detached HEAD (run git checkout <branch> first)");
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
109
121
|
// 5: fetch
|
|
110
122
|
const remote = tryExec("git remote", repoPath);
|
|
111
123
|
if (!remote.ok || !remote.out) {
|
|
@@ -115,22 +127,9 @@ function checkRepo(repoPath, name, level) {
|
|
|
115
127
|
const fetch = tryExec("git fetch -q \"" + r + "\"", repoPath);
|
|
116
128
|
if (!fetch.ok) {
|
|
117
129
|
problems.push("git fetch failed");
|
|
118
|
-
} else if (level === "workspace") {
|
|
119
|
-
const main = detectMainBranch(repoPath);
|
|
120
|
-
if (!main) {
|
|
121
|
-
problems.push("cannot detect main branch");
|
|
122
|
-
} else {
|
|
123
|
-
const behind = tryExec("git log HEAD..\"" + r + "/" + main + "\" --oneline", repoPath);
|
|
124
|
-
if (behind.ok && behind.out) {
|
|
125
|
-
problems.push("main branch has unpulled commits");
|
|
126
|
-
}
|
|
127
|
-
const current = tryExec("git rev-parse --abbrev-ref HEAD", repoPath);
|
|
128
|
-
if (current.ok && current.out !== main) {
|
|
129
|
-
problems.push("not on main branch (current " + current.out + ", expected " + main + ")");
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
130
|
} else {
|
|
133
|
-
//
|
|
131
|
+
// 轻量检查(workspace 与 app 一致):origin/<main> 引用可用即可。
|
|
132
|
+
// 分支基线恒为 origin/<main>(ADR 0002),不依赖本地检出/本地主分支状态。
|
|
134
133
|
const main = detectMainBranch(repoPath);
|
|
135
134
|
const refCheck = tryExec("git rev-parse --verify --quiet \"refs/remotes/" + r + "/" + main + "\"", repoPath);
|
|
136
135
|
if (!refCheck.ok) {
|
|
@@ -104,6 +104,14 @@ if (fs.existsSync(wtDir)) {
|
|
|
104
104
|
if (!created.ok) {
|
|
105
105
|
console.error('❌ workspace worktree 创建失败:' + created.err);
|
|
106
106
|
console.error(' 常见原因:分支已在其他 worktree 检出,或权限不足');
|
|
107
|
+
// 同名升级兜底识别:根检出当前分支 == 目标分支(不依赖 git 错误消息文本——
|
|
108
|
+
// 新老版本分别报 "already used by worktree" / "already checked out at")。
|
|
109
|
+
// 正常流程会被 Step 3.2 早退拦截,此处防绕过/直调脚本场景,给出针对性指引。
|
|
110
|
+
const curBranch = run('git rev-parse --abbrev-ref HEAD', wsRoot);
|
|
111
|
+
if (curBranch.ok && curBranch.out === branch) {
|
|
112
|
+
console.error(' ℹ️ 分支 ' + branch + ' 正被根检出占用(本地模式同名升级场景):先收尾本地模式');
|
|
113
|
+
console.error(' 或把根检出切回主干(主干名以远端 HEAD 为准,勿写死 main)后重跑本技能。');
|
|
114
|
+
}
|
|
107
115
|
process.exit(2);
|
|
108
116
|
}
|
|
109
117
|
console.log('✅ workspace worktree 已创建:' + wtDir);
|
|
@@ -5,22 +5,30 @@ require('./silence-popup');
|
|
|
5
5
|
*
|
|
6
6
|
* 用法:node update-gitignore.js <ws_root> <branch>
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
* 1.
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
8
|
+
* 行为(编号与代码内注释一致):
|
|
9
|
+
* 1. 检测当前分支
|
|
10
|
+
* 1.5 同名升级早退:目标分支正被根检出占用(origBranch == branch,本地模式升级
|
|
11
|
+
* worktree 场景)→ 零副作用退出(exit 2)——worktree add 必然失败(分支不能
|
|
12
|
+
* 双检出),且 checkout/commit 会打开无谓的副作用窗口;提示先收尾本地模式或
|
|
13
|
+
* 切回主干。detached 由 check-repos.js 前置拦截。
|
|
14
|
+
* 1.1 幂等检查:目标分支已在某 worktree 检出(init-worktree 重跑场景,上次已处理过
|
|
15
|
+
* .gitignore)时,跳过全部操作直接输出 GITIGNORE_OK——避免 checkout 与 worktree 检出冲突。
|
|
16
|
+
* 1.7 本地兜底:先把 .worktrees/ 与 .codespace/ ensure 进 .git/info/exclude(须在
|
|
17
|
+
* stash -u 之前——否则未忽略的 .codespace/ 大目录会被吞入 stash;同时消除
|
|
18
|
+
* 「.gitignore 条目在需求分支合并前、根检出仍显示 untracked」的存量空间噪音。跨分支即时生效、幂等)
|
|
19
|
+
* 2. stash 当前未提交修改(-u 含 untracked;仅 untracked 时不产生 stash)
|
|
20
|
+
* 2.1 目标分支不存在则从 <remote>/<main> 创建(首跑时分支尚未由 create-branches.js 建立,
|
|
21
|
+
* 顺序无关自愈;create-branches.js 后续会报 reused;基线不取 HEAD,见 ADR 0002)
|
|
22
|
+
* 3. 切换到目标分支
|
|
23
|
+
* 4. 检查 .worktrees/ 和 .codespace/ 是否在 .gitignore,缺失则添加并提交
|
|
24
|
+
* (检测按文件内容——1.7 的 info/exclude 兜底会让 git check-ignore 误报"已忽略")
|
|
25
|
+
* 5. 切回原分支
|
|
26
|
+
* 6. pop stash(如有)
|
|
19
27
|
*
|
|
20
28
|
* 退出码:
|
|
21
29
|
* 0 - 成功
|
|
22
30
|
* 1 - 参数错误
|
|
23
|
-
* 2 -
|
|
31
|
+
* 2 - 执行失败(含同名升级早退)
|
|
24
32
|
*/
|
|
25
33
|
|
|
26
34
|
const { execSync } = require('child_process');
|
|
@@ -51,6 +59,16 @@ if (!origBranch.ok) {
|
|
|
51
59
|
}
|
|
52
60
|
console.log('当前分支:' + origBranch.out);
|
|
53
61
|
|
|
62
|
+
// 1.5 同名升级早退:目标分支正被根检出占用(本地模式同名升级场景)。
|
|
63
|
+
// 此时 worktree add 必然失败(同一分支不能双检出),且后续 checkout/commit 打开无谓的
|
|
64
|
+
// 副作用窗口——直接零副作用退出,提示先收尾本地模式或切回主干(check-repos 轻量化后的唯一强制点)。
|
|
65
|
+
// 注意:detached HEAD 由 check-repos.js 拦截(origBranch 会是 "HEAD"),此处只需比对分支名。
|
|
66
|
+
if (origBranch.out === branch) {
|
|
67
|
+
console.error('❌ 目标分支 ' + branch + ' 正被根检出占用(本地模式同名升级场景)。');
|
|
68
|
+
console.error(' 请先收尾本地模式,或把根检出切回主干(主干名以远端 HEAD 为准,勿写死 main)后重跑本技能。');
|
|
69
|
+
process.exit(2);
|
|
70
|
+
}
|
|
71
|
+
|
|
54
72
|
// 1.1 幂等检查:目标分支已被某 worktree 检出 → 上次 init-worktree 已处理 .gitignore
|
|
55
73
|
const wtList = run('git worktree list --porcelain', wsRoot);
|
|
56
74
|
const branchInWorktree = wtList.ok && wtList.out.split(/\r?\n/).some(l => l.trim() === 'branch refs/heads/' + branch);
|
|
@@ -61,11 +79,36 @@ if (branchInWorktree) {
|
|
|
61
79
|
process.exit(0);
|
|
62
80
|
}
|
|
63
81
|
|
|
64
|
-
//
|
|
82
|
+
// 1.7 本地兜底:先把 .worktrees/ 与 .codespace/ ensure 进 .git/info/exclude。
|
|
83
|
+
// 必须在 stash -u 之前:让两个目录先被忽略,否则 -u 会把未忽略的 .codespace/
|
|
84
|
+
// 大目录(应用全量检出)一并吞入 stash——慢且有滞留风险。同时兜底消除
|
|
85
|
+
// 「.gitignore 条目在需求分支合并前、根检出仍显示 untracked」的存量空间噪音。
|
|
86
|
+
// exclude 与分支无关、本地即时生效、幂等;团队层条目仍走第 4 步的 .gitignore 提交。
|
|
87
|
+
const excludeRel = run('git rev-parse --git-path info/exclude', wsRoot);
|
|
88
|
+
if (excludeRel.ok && excludeRel.out) {
|
|
89
|
+
const excludePath = path.resolve(wsRoot, excludeRel.out);
|
|
90
|
+
fs.mkdirSync(path.dirname(excludePath), { recursive: true });
|
|
91
|
+
let exContent = fs.existsSync(excludePath) ? fs.readFileSync(excludePath, 'utf-8') : '';
|
|
92
|
+
const exLines = exContent.split(/\r?\n/).map(l => l.trim());
|
|
93
|
+
const exMissing = ['.worktrees/', '.codespace/']
|
|
94
|
+
.filter(d => !exLines.includes(d) && !exLines.includes(d.replace(/\/$/, '')));
|
|
95
|
+
if (exMissing.length > 0) {
|
|
96
|
+
if (exContent && !exContent.endsWith('\n')) exContent += '\n';
|
|
97
|
+
fs.writeFileSync(excludePath, exContent + exMissing.join('\n') + '\n');
|
|
98
|
+
console.log('✅ 已写入 .git/info/exclude(跨分支兜底):' + exMissing.join(', '));
|
|
99
|
+
} else {
|
|
100
|
+
console.log('ℹ️ .git/info/exclude 兜底条目已存在,跳过');
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
console.log('ℹ️ 无法定位 info/exclude,跳过本地兜底(不影响主流程)');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 2. stash 当前未提交修改(-u 含 untracked:防 checkout 被「untracked 与目标分支
|
|
107
|
+
// tracked 同名」拒绝;-u 前已 ensure exclude,.worktrees/ / .codespace/ 不会被吞)
|
|
65
108
|
let stashed = false;
|
|
66
109
|
const status = run('git status --porcelain', wsRoot);
|
|
67
110
|
if (status.ok && status.out) {
|
|
68
|
-
const stash = run('git stash push -m "init-worktree-temp"', wsRoot);
|
|
111
|
+
const stash = run('git stash push -u -m "init-worktree-temp"', wsRoot);
|
|
69
112
|
if (stash.ok) {
|
|
70
113
|
if (/No local changes to save/.test(stash.out)) {
|
|
71
114
|
console.log('ℹ️ 仅 untracked 文件,无需 stash');
|
|
@@ -115,15 +158,16 @@ if (!checkout.ok) {
|
|
|
115
158
|
console.log('✅ 切换到分支:' + branch);
|
|
116
159
|
|
|
117
160
|
// 4. 检查并添加 .worktrees/ 和 .codespace/ 到 .gitignore
|
|
161
|
+
// 检测用文件内容(而非 git check-ignore——1.7 的 info/exclude 兜底会让 check-ignore
|
|
162
|
+
// 报告"已忽略",从而永远跳过团队层 .gitignore 提交);此处判断的是 tracked 文件自身条目。
|
|
118
163
|
const gitignorePath = path.join(wsRoot, '.gitignore');
|
|
119
164
|
let content = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, 'utf-8') : '';
|
|
120
|
-
const original = content;
|
|
121
165
|
const needCommit = [];
|
|
122
166
|
|
|
123
167
|
for (const dir of ['.worktrees/', '.codespace/']) {
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
168
|
+
const lines = content.split(/\r?\n/).map(l => l.trim());
|
|
169
|
+
const hasEntry = lines.includes(dir) || lines.includes(dir.replace(/\/$/, ''));
|
|
170
|
+
if (!hasEntry) {
|
|
127
171
|
if (content && !content.endsWith('\n')) content += '\n';
|
|
128
172
|
content += dir + '\n';
|
|
129
173
|
needCommit.push(dir);
|