@jspg-ai/coding-bb 0.0.3-beta.32 → 0.0.3-beta.34
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 +6 -5
- package/cbb/lib/utils/githook.js +6 -2
- package/cbb/worktrees/_shared/scripts/check-unarchived.js +77 -0
- package/cbb/worktrees/_shared/scripts/delete-branches.js +147 -0
- package/cbb/worktrees/_shared/scripts/deprecate-branches.js +119 -0
- package/cbb/worktrees/_shared/scripts/find-target-worktree.js +1 -1
- package/cbb/worktrees/_shared/scripts/find-workspace-root.js +1 -1
- package/cbb/worktrees/_shared/scripts/install-git-hook.js +1 -1
- package/cbb/worktrees/_shared/scripts/merge-branches.js +469 -0
- package/cbb/worktrees/_shared/scripts/push-core.js +1 -1
- package/cbb/worktrees/_shared/scripts/silence-popup.js +1 -1
- package/cbb/worktrees/skills/cbb-local-mode/SKILL.md +46 -10
- package/cbb/worktrees/skills/cbb-local-mode/scripts/delete-branches.js +147 -0
- package/cbb/worktrees/skills/cbb-local-mode/scripts/deprecate-branches.js +119 -0
- package/cbb/worktrees/skills/cbb-local-mode/scripts/install-git-hook.js +1 -1
- package/cbb/worktrees/skills/cbb-local-mode/scripts/merge-branches.js +469 -0
- package/cbb/worktrees/skills/cbb-local-mode/scripts/silence-popup.js +1 -1
- package/cbb/worktrees/skills/cbb-worktree-close/SKILL.md +88 -52
- package/cbb/worktrees/skills/cbb-worktree-close/scripts/check-env.js +1 -1
- package/cbb/worktrees/skills/cbb-worktree-close/scripts/check-unarchived.js +3 -1
- package/cbb/worktrees/skills/cbb-worktree-close/scripts/delete-branches.js +147 -136
- package/cbb/worktrees/skills/cbb-worktree-close/scripts/deprecate-branches.js +119 -0
- package/cbb/worktrees/skills/cbb-worktree-close/scripts/find-target-worktree.js +1 -1
- package/cbb/worktrees/skills/cbb-worktree-close/scripts/find-workspace-root.js +1 -1
- package/cbb/worktrees/skills/cbb-worktree-close/scripts/merge-branches.js +469 -0
- package/cbb/worktrees/skills/cbb-worktree-close/scripts/safety-check.js +4 -2
- package/cbb/worktrees/skills/cbb-worktree-close/scripts/silence-popup.js +1 -1
- package/cbb/worktrees/skills/cbb-worktree-init/SKILL.md +13 -10
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/install-git-hook.js +1 -1
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/push-core.js +1 -1
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/silence-popup.js +1 -1
- package/cbb/worktrees/skills/cbb-worktree-push/SKILL.md +143 -20
- package/cbb/worktrees/skills/cbb-worktree-push/scripts/check-unarchived.js +77 -0
- package/cbb/worktrees/skills/cbb-worktree-push/scripts/find-target-worktree.js +1 -1
- package/cbb/worktrees/skills/cbb-worktree-push/scripts/find-workspace-root.js +1 -1
- package/cbb/worktrees/skills/cbb-worktree-push/scripts/merge-branches.js +469 -0
- package/cbb/worktrees/skills/cbb-worktree-push/scripts/push-core.js +1 -1
- package/cbb/worktrees/skills/cbb-worktree-push/scripts/silence-popup.js +1 -1
- package/config/openspec/schemas/spec-driven/schema.yaml +1 -0
- package/package.json +2 -2
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/find-workspace-root.js +0 -67
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: cbb-worktree-close
|
|
3
|
-
description: "关闭/清理/删除需求工作空间的 worktree
|
|
3
|
+
description: "关闭/清理/删除需求工作空间的 worktree 和分支。内置收尾合并门禁(ADR 0007):未合并分支必须先经 MR 或直合并交付,或由用户确认废弃并重命名为 <分支>-deprecated,否则不允许关闭。触发:用户明确说关闭/清理/删除 worktree、结束隔离开发、合并完要清理。禁止在 cbb-worktree-init 执行后自动调用——只有用户主动要求关闭时才触发。"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Close Worktree(跨平台脚本化版本 v2.0)
|
|
@@ -17,7 +17,9 @@ description: "关闭/清理/删除需求工作空间的 worktree 和分支。触
|
|
|
17
17
|
- 只操作 `.worktrees/worktree-<需求名>` 这一类精确路径,**绝不碰其它 worktree**(如 IDE/工具自动创建的 `~/.qoder/worktree/...` 会话 worktree)
|
|
18
18
|
- 通过检查 `.worktrees/worktree-<需求名>/.codespace/<应用名>/` **是否实际存在**来确定关联应用——创建时用户可能只关联了 `CBB-WORKSPACE.json` 中的部分应用,不能假设全部应用都关联
|
|
19
19
|
- **顺序硬约束:先移除所有 worktree,再删除分支。** 目标分支当前正检出于 worktree 中,先删分支会报 `error: Cannot delete branch '<分支>' checked out at '<worktree 路径>'`
|
|
20
|
-
-
|
|
20
|
+
- **收尾合并门禁(ADR 0007)**:关闭前每个参与仓库必须为"已合并"(复用 `safety-check` 的 `merged` 判定,含 release 中转 / squash 兜底)。未合并时由用户在门禁处三选一——**提 MR**(人工浏览器合入)/ **直合并**(agent 执行 `merge-branches.js`,前置:已归档 + 已提交)/ **确认废弃**(`deprecate-branches.js` 把远程分支重命名为 `<分支>-deprecated`)。**硬门禁:未合并且非废弃一律不允许关闭**
|
|
21
|
+
- **合并成功后自动删分支(ADR 0007)**:交付完成(MR 已合并或直合并成功)后自动删各参与仓库的本地 + 远程 feature 分支(删除前输出将删清单);"废弃"出口则保留分支(仅远程重命名)
|
|
22
|
+
- **破坏性操作必须先报告、经用户确认才执行**:未提交修改、未确认废弃的分支,绝不静默 `--force` 或 `-D` 丢弃
|
|
21
23
|
- 软性检查失败时报告详情,询问用户是否继续
|
|
22
24
|
- 输入约定:凡是需要用户自由输入文本(如分支名、确认)的场景,**必须直接在对话中提问并等待用户回复**,禁止使用 `AskUserQuestion` 工具;`AskUserQuestion` 仅用于固定选项场景(选择归档哪些 change、是否丢弃未提交修改)
|
|
23
25
|
|
|
@@ -224,60 +226,82 @@ AskUserQuestion({
|
|
|
224
226
|
- 若 delta specs 需要同步 → 对该 change 单独提示同步选项(同步 / 跳过同步直接归档)
|
|
225
227
|
- 每个 change 的归档结果独立记录(✅ 已归档 / ⏭️ 跳过 / ❌ 失败)
|
|
226
228
|
|
|
227
|
-
### 3.3
|
|
229
|
+
### 3.3 未推送 commit 处理(仅当 2.4 任一 worktree `unpushed_count > 0` 时出现)
|
|
228
230
|
|
|
229
|
-
|
|
231
|
+
**场景**:用户在 worktree 中做了 commit,但没有调 `cbb-worktree-push` 推送,本地有 ahead 提交。
|
|
230
232
|
|
|
231
|
-
|
|
233
|
+
**报告**:
|
|
234
|
+
```
|
|
235
|
+
⚠️ 检测到未推送的提交(ahead of origin/<branch>):
|
|
236
|
+
- workspace: 3 个 commit
|
|
237
|
+
- ep-user-info-server: 1 个 commit
|
|
238
|
+
|
|
239
|
+
请选择处理方式:
|
|
240
|
+
[先推送](推荐)调 cbb-worktree-push 推送所有 ahead 提交
|
|
241
|
+
[继续] 不单独推送,直接进入合并门禁(走「直合并」时会随合并一并推送到主干)
|
|
242
|
+
[中止] 终止流程,我先手动处理
|
|
243
|
+
```
|
|
232
244
|
|
|
233
|
-
- `
|
|
234
|
-
- Step
|
|
245
|
+
- 用户选择「先推送」→ AI 引导执行 `cbb-worktree-push` skill(或 `/cbb-worktree-push`),完成后**重新跑** Step 2 safety-check 确认 `unpushed_count = 0` 再回到本步骤
|
|
246
|
+
- 用户选择「继续」→ 进入 Step 3.4;注意:走「提 MR」出口时会因 feature 未推送而失败,需回到本步骤先推送
|
|
247
|
+
- 用户选择「中止」→ 终止流程
|
|
235
248
|
|
|
236
|
-
|
|
249
|
+
> ⓘ 走「直合并」出口时,ahead 提交会被合并进主干并随 `git push origin <主干>` 一并推送,无需单独推送 feature 分支。
|
|
237
250
|
|
|
238
|
-
|
|
251
|
+
### 3.4 合并门禁决策(核心 · ADR 0007)
|
|
239
252
|
|
|
240
|
-
|
|
241
|
-
# 仅删本地分支(已合并自动 -d,未合并自动 -D;含 squash 合并 -D 兜底)
|
|
242
|
-
node "<skill_dir>/scripts/delete-branches.js" "<ws_root>" "<branch>" local
|
|
253
|
+
依据 Step 2 的 `merged` 判定(每个参与仓库 `merged !== null` 即已合并,含 `main` / `origin/main` / `main-via-release-*`):
|
|
243
254
|
|
|
244
|
-
|
|
245
|
-
node "<skill_dir>/scripts/delete-branches.js" "<ws_root>" "<branch>" local_remote
|
|
246
|
-
```
|
|
255
|
+
**情况 A:全部参与仓库已合并** → 无交互,直接进入 Step 4(合并成功后自动删分支)。
|
|
247
256
|
|
|
248
|
-
|
|
257
|
+
**情况 B:存在未合并仓库** → 硬门禁触发,`AskUserQuestion` 三选一(**不允许"未合并且非废弃"地关闭**):
|
|
249
258
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
-
|
|
259
|
+
```
|
|
260
|
+
以下仓库的需求分支尚未交付(未合并到主干):
|
|
261
|
+
- workspace(merged: null)
|
|
262
|
+
- ep-foo-app(merged: null)
|
|
263
|
+
|
|
264
|
+
请选择交付方式:
|
|
265
|
+
[提 MR](推荐) 生成预填合并请求链接,由人工在浏览器合入
|
|
266
|
+
[直合并] 由 agent 直接合并到主干并推送远端(需你确认)
|
|
267
|
+
[确认废弃] 该分支不再交付,远程重命名为 <分支>-deprecated
|
|
268
|
+
```
|
|
253
269
|
|
|
254
|
-
|
|
270
|
+
**出口一 · 提 MR(推荐)**
|
|
255
271
|
|
|
256
|
-
|
|
272
|
+
1. 先确保 feature 已推送(Step 3.3 未推送时先推送)
|
|
273
|
+
2. **引导用户运行 `cbb-worktree-push`**(说"提交并推送"):其 Step 7 会列出目标分支候选(`git branch -r` 现场候选,**禁止 AI 代决**)并生成 MR 预填链接(可选自动开浏览器);本技能不自带 MR 链接脚本
|
|
274
|
+
3. **中止 close**:提示"合并完成后重跑本技能"。**不执行任何移除 / 删除**
|
|
257
275
|
|
|
258
|
-
|
|
259
|
-
- 用户在 worktree 中做了 commit,但**没有调** `cbb-worktree-push` 推送
|
|
260
|
-
- 本地有 ahead 提交未推送到 origin
|
|
261
|
-
- 一旦 close worktree + 后续走 `local` / `local_remote` 删分支,ahead 提交可能永久丢失(取决于上游 `delete-branches.js` 行为)
|
|
276
|
+
**出口二 · 直合并(`merge-branches.js`)**
|
|
262
277
|
|
|
263
|
-
|
|
278
|
+
- **前置(Q18)**:3.1 无未提交修改、3.2 已归档并提交——**合并前必须已归档 + 已提交**(两者先后不限)
|
|
279
|
+
- 执行(`--apps` 传 Step 1.2 发现的参与应用):
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
node "<skill_dir>/scripts/merge-branches.js" "<ws_root>" "<branch>" --apps "<app1,app2>"
|
|
264
283
|
```
|
|
265
|
-
⚠️ 检测到未推送的提交(ahead of origin/<branch>):
|
|
266
|
-
- workspace: 3 个 commit
|
|
267
|
-
- ep-user-info-server: 1 个 commit
|
|
268
|
-
- ep-baz-app: 2 个 commit
|
|
269
284
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
285
|
+
- 两阶段:预检(主干可探测 / 主干检出干净且不领先 origin / 分支存在;全过才执行)→ 执行(checkout 主干 → pull --ff-only → merge --no-ff → push origin 主干)
|
|
286
|
+
- 解读末行 `MERGE_RESULT`:
|
|
287
|
+
- 退出码 0 → 交付完成,继续 Step 4
|
|
288
|
+
- `need-mr`(`branch-protected`:远端主干受保护)→ 远端保护是常态,**改用出口一**(不是故障)
|
|
289
|
+
- `failed`(含 `merge-failed` / `push-failed`)→ 输出失败明细,阻塞并询问用户(修复后重跑;重跑幂等)
|
|
290
|
+
- `failed` 且 `not-on-main`(空间根检出不是主干:被其他需求占用或异常)→ 先让该占用回到主干(收尾本地需求)或改用出口一;**不要用 `--allow-checkout`**(会切走占用者的工作区)
|
|
291
|
+
|
|
292
|
+
**出口三 · 确认废弃(`deprecate-branches.js`)**
|
|
293
|
+
|
|
294
|
+
- **二次确认**(远程重命名不可逆):`AskUserQuestion` 固定选项"确认废弃 / 中止"
|
|
295
|
+
- 执行:
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
node "<skill_dir>/scripts/deprecate-branches.js" "<ws_root>" "<branch>" --apps "<app1,app2>"
|
|
274
299
|
```
|
|
275
300
|
|
|
276
|
-
-
|
|
277
|
-
-
|
|
278
|
-
- 用户选择「中止」→ 终止流程
|
|
301
|
+
- 效果:远程存在该分支的仓库,重命名 `<分支>` → `<分支>-deprecated`(**保留历史、不删提交**);**本地分支保持原名不动**
|
|
302
|
+
- 解读末行 `DEPRECATE_RESULT`:`failed` → 报告并终止
|
|
279
303
|
|
|
280
|
-
> ⓘ
|
|
304
|
+
> ⓘ 门禁判定复用 `safety-check` 的 `merged`:他人经 MR 或 `release-*` 中转合入后,重跑本技能即自动放行,无需重复交付。
|
|
281
305
|
|
|
282
306
|
---
|
|
283
307
|
|
|
@@ -341,32 +365,34 @@ node "<skill_dir>/scripts/remove-worktrees.js "<ws_root>" "<branch>" "<force>"
|
|
|
341
365
|
- **不会产生孤立窗口**——`--reuse-window` 是复用当前窗口切换目录,**不会打开新窗口**。即使 IDE 已开着指向 WS_ROOT 的其他窗口,本步骤也不会重复创建,仅是把当前指向已删除目录的窗口切走
|
|
342
366
|
- **失败不致命**——`session_move` / `--reuse-window` 失败时仅警告,后续分支删除不受影响
|
|
343
367
|
|
|
344
|
-
### 4.4
|
|
368
|
+
### 4.4 分支处理(合并成功后自动删除 · ADR 0007)
|
|
345
369
|
|
|
346
|
-
|
|
370
|
+
**按 Step 3.4 的门禁出口决定**:
|
|
347
371
|
|
|
348
|
-
|
|
372
|
+
- **交付完成**(情况 A 已合并,或出口二直合并成功)→ **自动删除**各参与仓库的本地 + 远程 feature 分支:
|
|
349
373
|
|
|
350
374
|
```bash
|
|
351
|
-
node "<skill_dir>/scripts/delete-branches.js" "<ws_root>" "<branch>" local
|
|
352
|
-
# 或
|
|
353
375
|
node "<skill_dir>/scripts/delete-branches.js" "<ws_root>" "<branch>" local_remote
|
|
354
376
|
```
|
|
355
377
|
|
|
378
|
+
- **出口三废弃** → **保留分支**(远程已重命名为 `<分支>-deprecated`),**不调用** `delete-branches.js`
|
|
379
|
+
|
|
380
|
+
**删除前必做**:输出"即将删除的本地 / 远程分支清单"(逐仓库),透明可见。
|
|
381
|
+
|
|
356
382
|
脚本内部对每个仓库:
|
|
357
383
|
- **本地分支**:检测是否已合并(复用 safety-check 的合并判定算法)
|
|
358
384
|
- 已合并 → `git branch -d`(安全删除)
|
|
359
385
|
- 未合并 → `git branch -D`(强制删除)
|
|
360
386
|
- squash 合并可能让 `-d` 失败 → 自动回退到 `-D`(squash 合并同样安全)
|
|
361
|
-
-
|
|
387
|
+
- **远程分支**(`local_remote` 模式):`git push <remote> --delete <branch>`
|
|
362
388
|
- 分支不存在时 push --delete 会报错,按"已删除/本就无"处理即可
|
|
363
389
|
- 远程引用已在 Step 2.0 主动 fetch 刷新并持久化在 .git,此处直接读取无需重复 fetch
|
|
364
390
|
|
|
365
|
-
**解读末行 `BRANCH_DELETE_RESULT
|
|
366
|
-
|
|
367
|
-
|
|
391
|
+
**解读末行 `BRANCH_DELETE_RESULT`**:`mode`(keep / local / local_remote)、`ok`。
|
|
392
|
+
|
|
393
|
+
**失败处理(ADR 0007)**:清理阶段(删除分支)失败**报告并继续其余仓库,不回滚**——交付已达成,清理不净不该阻塞(与合并阶段的"失败即停"不同)。
|
|
368
394
|
|
|
369
|
-
|
|
395
|
+
**顺序硬约束**:必须在 Step 4.2(移除 worktree)**之后**执行——分支正检出于 worktree,先删会报 `Cannot delete branch ... checked out at ...`。
|
|
370
396
|
|
|
371
397
|
> 关于 `.gitignore`:创建时把 `.worktrees/` 加入 `.gitignore` 并**提交到了 feature 分支**(非主分支)。
|
|
372
398
|
> - 分支未合并、被 `-D` 删除 → 这些 commit 随分支一起消失,主分支的 `.gitignore` 不受影响
|
|
@@ -395,6 +421,7 @@ git -C "<ws_root>" worktree list
|
|
|
395
421
|
```
|
|
396
422
|
✅ 工作空间 worktree 已关闭
|
|
397
423
|
当前目录:<工作空间根目录绝对路径>
|
|
424
|
+
交付方式:已合并(MR 合入)/ 直合并(agent 合并并推送主干)/ 确认废弃
|
|
398
425
|
已移除 worktree:
|
|
399
426
|
- .worktrees/worktree-feature-xxx(workspace)
|
|
400
427
|
- .worktrees/worktree-feature-xxx/.codespace/ep-user-info-server(嵌套,ep-user-info-server 仓库)
|
|
@@ -404,7 +431,9 @@ git -C "<ws_root>" worktree list
|
|
|
404
431
|
✅ ai-call-send-im-msg → 已归档到 archive/2026-07-08-ai-call-send-im-msg
|
|
405
432
|
⏭️ fix-payment-timeout → 用户选择跳过
|
|
406
433
|
(无未归档 change 时不显示本节)
|
|
407
|
-
分支处理:
|
|
434
|
+
分支处理:
|
|
435
|
+
- 交付完成 → 已删除本地 + 远程分支:feature-xxx(逐仓库列出)
|
|
436
|
+
- 已废弃 → 远程已重命名为 feature-xxx-deprecated(本地分支保留原名)
|
|
408
437
|
剩余工作空间 worktree:<列出仍存在的,或 "无" >
|
|
409
438
|
👉 仅当所用工具不支持自动切根、且你的窗口仍停在已删除的 worktree 时,请手动切回工作空间根:<ws_root 绝对路径>
|
|
410
439
|
Workspace cleaned up.
|
|
@@ -425,7 +454,10 @@ git -C "<ws_root>" worktree list
|
|
|
425
454
|
### 绝不允许
|
|
426
455
|
|
|
427
456
|
- 先删分支再删 worktree(分支正检出于 worktree,会报 `Cannot delete branch ... checked out at ...`)
|
|
428
|
-
-
|
|
457
|
+
- **未合并且未确认为废弃时关闭 worktree**(违反 ADR 0007 硬门禁)
|
|
458
|
+
- 未经用户确认用 `--force` 丢弃未提交修改;或对未确认废弃的未合并分支执行 `-D` 强删
|
|
459
|
+
- 未经二次确认执行 `deprecate-branches.js`(远程重命名不可逆)
|
|
460
|
+
- 直合并前跳过"已归档 + 已提交"前置校验
|
|
429
461
|
- 操作 `.worktrees/worktree-<需求名>` 以外的任何 worktree(如 `~/.qoder/worktree/...` 会话 worktree)
|
|
430
462
|
- 移除/删除前跳过 Step 2 安全评估
|
|
431
463
|
- 删除工作空间根目录的 AI 配置目录(`.claude/`、`.qoder/`、`.opencode/` 等)
|
|
@@ -437,6 +469,8 @@ git -C "<ws_root>" worktree list
|
|
|
437
469
|
|
|
438
470
|
- 读 `CBB-WORKSPACE.json` 取应用名列表;以 `.worktrees/worktree-<需求名>/.codespace/<应用名>/` 实际存在性确定关联清单
|
|
439
471
|
- 移除/删除前执行安全评估(未提交修改 / 是否合并 / 是否推远程)并报告
|
|
472
|
+
- **按 Step 3.4 门禁三选一交付**;未合并且未确认为废弃时**禁止关闭**
|
|
473
|
+
- 交付完成(自动删分支)前**先输出将删清单**;清理阶段失败报告并继续,不回滚
|
|
440
474
|
- 严格按 Step 4.2(移除 worktree)→ Step 4.4(删分支)顺序
|
|
441
475
|
- 移除 workspace worktree 前先把会话移出(`cd "<WS_ROOT>" &&` 或 `git -C` 绝对路径)
|
|
442
476
|
- 收尾切回 WS 根目录、`pwd` 确认
|
|
@@ -460,7 +494,9 @@ git -C "<ws_root>" worktree list
|
|
|
460
494
|
| `find-workspace-root.js` | 定位工作空间根目录(共享脚本,源仓库真源在 `_shared/scripts/`) | 无 | 0/1 |
|
|
461
495
|
| `find-target-worktree.js` | 目标 worktree 识别(共享脚本,源仓库真源在 `_shared/scripts/`) | ws_root | 0/1/2 |
|
|
462
496
|
| `discover-apps.js` | 发现关联应用 worktree | ws_root, branch | 0/1/2 |
|
|
463
|
-
| `check-unarchived.js` | 检查未归档 change | ws_root | 0/1 |
|
|
497
|
+
| `check-unarchived.js` | 检查未归档 change(双扫空间根 + worktree) | ws_root, branch | 0/1 |
|
|
464
498
|
| `safety-check.js` | 安全评估(合并判定 + 未提交 + 远程) | ws_root, branch | 0/1 |
|
|
465
499
|
| `remove-worktrees.js` | 移除 worktree(三层降级) | ws_root, branch, force | 0/1 |
|
|
466
|
-
| `delete-branches.js` | 删除分支(按 mode
|
|
500
|
+
| `delete-branches.js` | 删除分支(按 mode;交付完成后由 Step 4.4 自动以 local_remote 调用) | ws_root, branch, mode | 0/1/2 |
|
|
501
|
+
| `merge-branches.js` | 收尾合并门禁·直合并(预检 → merge --no-ff → push 主干,共享脚本,真源在 `_shared/scripts/`) | ws_root, branch, --apps / --target / --allow-checkout / --dry-run / --check | 0/1/2 |
|
|
502
|
+
| `deprecate-branches.js` | 收尾合并门禁·废弃(远程分支重命名为 `<分支>-deprecated`,共享脚本) | ws_root, branch, --apps | 0/1/2 |
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
require('./silence-popup');
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* 未归档 change 检查(共享脚本,真源:cbb/worktrees/_shared/scripts/)
|
|
5
|
+
*
|
|
6
|
+
* 供 cbb-worktree-close(收尾安全评估)与 cbb-worktree-push(Step 7 直合并前置,ADR 0008)调用。
|
|
5
7
|
*
|
|
6
8
|
* 用法:node check-unarchived.js <ws_root> [branch]
|
|
7
9
|
*
|
|
@@ -1,136 +1,147 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
require('./silence-popup');
|
|
3
|
-
/**
|
|
4
|
-
* cbb
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (
|
|
62
|
-
if (
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
require('./silence-popup');
|
|
3
|
+
/**
|
|
4
|
+
* 删除需求分支(共享脚本,真源:cbb/worktrees/_shared/scripts/)
|
|
5
|
+
*
|
|
6
|
+
* 供 cbb-worktree-close(Step 4.4 交付完成后自动删本地 + 远程分支)与
|
|
7
|
+
* cbb-local-mode(Step 5.4 收尾删远程分支)调用。
|
|
8
|
+
*
|
|
9
|
+
* 用法:node delete-branches.js <ws_root> <branch> <mode> # mode: keep / local / local_remote
|
|
10
|
+
*
|
|
11
|
+
* 逐仓库执行:本地分支按合并判定选 -d / -D(squash 合并 -d 失败回退 -D);
|
|
12
|
+
* local_remote 模式追加 git push <remote> --delete。
|
|
13
|
+
* 清理阶段失败报告并继续其余仓库,不回滚(ADR 0007)。
|
|
14
|
+
*/
|
|
15
|
+
const { execSync } = require('child_process');
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
|
|
19
|
+
const wsRoot = process.argv[2];
|
|
20
|
+
const branch = process.argv[3];
|
|
21
|
+
const mode = process.argv[4];
|
|
22
|
+
|
|
23
|
+
if (!wsRoot || !branch || !mode) {
|
|
24
|
+
console.error('Usage: node delete-branches.js <ws_root> <branch> <mode>');
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (mode === 'keep') {
|
|
29
|
+
console.log('Keep branch, skip delete');
|
|
30
|
+
console.log('---');
|
|
31
|
+
console.log('BRANCH_DELETE_RESULT', JSON.stringify({ mode: 'keep', ok: true, deleted: [] }));
|
|
32
|
+
process.exit(0);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (mode !== 'local' && mode !== 'local_remote') {
|
|
36
|
+
console.error('mode must be keep / local / local_remote');
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function run(cmd, cwd, env) {
|
|
41
|
+
try {
|
|
42
|
+
return { ok: true, out: execSync(cmd, { cwd, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], env: { ...process.env, ...(env || {}) } }).trim() };
|
|
43
|
+
} catch (e) {
|
|
44
|
+
return { ok: false, err: (e.stderr || e.message || String(e)).trim() };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function detectMain(repoPath) {
|
|
49
|
+
const remote = run('git remote', repoPath);
|
|
50
|
+
if (!remote.ok || !remote.out) return 'main';
|
|
51
|
+
const r = remote.out.split(/\r?\n/)[0];
|
|
52
|
+
const symbolic = run('git symbolic-ref "refs/remotes/' + r + '/HEAD"', repoPath);
|
|
53
|
+
if (symbolic.ok && symbolic.out) {
|
|
54
|
+
return symbolic.out.replace('refs/remotes/' + r + '/', '');
|
|
55
|
+
}
|
|
56
|
+
return 'main';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function mergedTo(repoPath, from, to) {
|
|
60
|
+
// 0=merged, 1=unmerged, 2=to-ref-missing
|
|
61
|
+
if (!run('git rev-parse --verify --quiet "' + from + '"', repoPath).ok) return 1;
|
|
62
|
+
if (!run('git rev-parse --verify --quiet "' + to + '"', repoPath).ok) return 2;
|
|
63
|
+
if (run('git merge-base --is-ancestor "' + from + '" "' + to + '" && echo Y', repoPath).out === 'Y') return 0;
|
|
64
|
+
// 跨平台:不用 grep(Windows 无 grep),解析 git cherry 输出(+ 前缀 = 未合并提交)
|
|
65
|
+
const cherry = run('git cherry "' + to + '" "' + from + '"', repoPath);
|
|
66
|
+
if (!cherry.ok) return 1;
|
|
67
|
+
if (cherry.out.split(/\r?\n/).filter(Boolean).some(l => l.indexOf('+') === 0)) return 1;
|
|
68
|
+
return 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function mergedToMain(repoPath, branchName, main) {
|
|
72
|
+
if (mergedTo(repoPath, branchName, main) === 0) return true;
|
|
73
|
+
if (mergedTo(repoPath, branchName, 'origin/' + main) === 0) return true;
|
|
74
|
+
// release-* transit
|
|
75
|
+
const releaseList = run('git for-each-ref --format="%(refname:short)" "refs/heads/release-*" "refs/remotes/origin/release-*"', repoPath);
|
|
76
|
+
if (releaseList.ok && releaseList.out) {
|
|
77
|
+
const releases = releaseList.out.split(/\r?\n/).filter(Boolean).sort();
|
|
78
|
+
for (const rel of releases) {
|
|
79
|
+
if (mergedTo(repoPath, branchName, rel) === 0) {
|
|
80
|
+
if (mergedTo(repoPath, rel, main) === 0) return true;
|
|
81
|
+
if (mergedTo(repoPath, rel, 'origin/' + main) === 0) return true;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function deleteBranch(repoPath, name) {
|
|
89
|
+
const checkLocal = run('git branch --list "' + branch + '"', repoPath);
|
|
90
|
+
if (checkLocal.ok && checkLocal.out) {
|
|
91
|
+
const main = detectMain(repoPath);
|
|
92
|
+
const merged = main ? mergedToMain(repoPath, branch, main) : false;
|
|
93
|
+
let flag = merged ? '-d' : '-D';
|
|
94
|
+
const del = run('git branch ' + flag + ' "' + branch + '"', repoPath);
|
|
95
|
+
if (del.ok) {
|
|
96
|
+
console.log('OK ' + name + ' local branch deleted (' + flag + ', ' + (merged ? 'merged' : 'unmerged') + ')');
|
|
97
|
+
} else {
|
|
98
|
+
if (flag === '-d') {
|
|
99
|
+
const fallback = run('git branch -D "' + branch + '"', repoPath);
|
|
100
|
+
if (fallback.ok) {
|
|
101
|
+
console.log('OK ' + name + ' local branch deleted (squash merge, fallback -D)');
|
|
102
|
+
} else {
|
|
103
|
+
console.error('FAIL ' + name + ' local branch delete failed');
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
console.error('FAIL ' + name + ' local branch delete failed');
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
console.log('Skip ' + name + ': no local branch ' + branch);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (mode === 'local_remote') {
|
|
116
|
+
const remote = run('git remote', repoPath);
|
|
117
|
+
if (remote.ok && remote.out) {
|
|
118
|
+
const r = remote.out.split(/\r?\n/)[0];
|
|
119
|
+
const pushDel = run('git push "' + r + '" --delete "' + branch + '"', repoPath, { GIT_TERMINAL_PROMPT: '0' });
|
|
120
|
+
if (pushDel.ok) {
|
|
121
|
+
console.log('OK ' + name + ' remote branch deleted');
|
|
122
|
+
} else {
|
|
123
|
+
console.log('Skip ' + name + ': remote has no ' + branch);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const configPath = path.join(wsRoot, 'CBB-WORKSPACE.json');
|
|
131
|
+
let config = {};
|
|
132
|
+
try { config = JSON.parse(fs.readFileSync(configPath, 'utf-8')); } catch (e) {}
|
|
133
|
+
|
|
134
|
+
const apps = config.apps || [];
|
|
135
|
+
|
|
136
|
+
console.log('=== Delete Workspace Branch ===');
|
|
137
|
+
deleteBranch(wsRoot, 'workspace');
|
|
138
|
+
|
|
139
|
+
console.log('=== Delete App Branches ===');
|
|
140
|
+
for (const app of apps) {
|
|
141
|
+
const appPath = path.join(wsRoot, '.codespace', app.name);
|
|
142
|
+
deleteBranch(appPath, app.name);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
console.log('---');
|
|
146
|
+
console.log('BRANCH_DELETE_RESULT', JSON.stringify({ mode, ok: true }));
|
|
147
|
+
process.exit(0);
|