@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
|
@@ -17,7 +17,7 @@ description: "为一个需求在 workspace 与各关联应用仓库同步创建
|
|
|
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 生效
|
|
19
19
|
- **本技能必须在业务工作空间根目录执行**(即由 `cbb setup` 初始化、包含 `CBB-WORKSPACE.json` 的目录)
|
|
20
|
-
-
|
|
20
|
+
- 输入约定:**自由文本**(需求名、需求描述等)直接在对话中提问并等待回复,禁止使用 `AskUserQuestion`;`AskUserQuestion` 仅用于固定选项决策场景(如 Step 3.1 候选名选择、push 失败处理、远程分支已存在时的二选一)
|
|
21
21
|
- 关联应用恒为 `CBB-WORKSPACE.json` 的**全量 apps**,AI 不询问范围、不从需求语境收窄(worktree 单仓库创建成本低,全量镜像空间根布局);仅当**用户显式点名**应用时才经 `--apps` 传子集(见 Step 1),需要增减应用时编辑该文件并重跑本技能(幂等)
|
|
22
22
|
- **模式选择(ADR 0002)**:接到需求先判断用户要哪种模式——要**并行 / 隔离** → 本技能;**单线程快改、当前会话就地干** → 引导 `cbb-local-mode`。用户明确指定即遵从,agent 只给推荐不代决
|
|
23
23
|
|
|
@@ -207,17 +207,20 @@ node "<skill_dir>/scripts/check-repos.js" "<ws_root>"
|
|
|
207
207
|
|
|
208
208
|
### 3.1 询问需求名
|
|
209
209
|
|
|
210
|
-
**直接在对话中**向用户提问(不要使用 `AskUserQuestion` 工具):*"请输入需求名(同时作为分支名和 workspace
|
|
210
|
+
**直接在对话中**向用户提问(不要使用 `AskUserQuestion` 工具):*"请输入需求名(同时作为分支名和 workspace 工作树目录名);也可以输入一段需求描述,我来推荐几个英文名供你选择:"*,然后等待用户的纯文本回复。
|
|
211
|
+
|
|
212
|
+
**两种输入:**
|
|
213
|
+
- **直接输入名字** → 按下方命名规则校验后采用
|
|
214
|
+
- **输入需求描述**(中英文不限)→ agent 生成 **3–5 个** kebab-case 英文候选(语义对应描述,可按需带类型前缀 `feature-` / `fix-` / `refactor-` 等,候选间风格拉开),用 `AskUserQuestion` 列出供选择;不满意可直接回复自己的名字 → 获选名按命名规则校验后采用
|
|
215
|
+
|
|
216
|
+
> 示例:描述"修复 2026 Q2 数据泄漏" → 候选 `fix-2026q2-data-leak`、`fix-q2-data-leak`、`patch-data-leak-2026q2`
|
|
211
217
|
|
|
212
218
|
**命名规则:**
|
|
213
219
|
- 推荐 kebab-case,如 `feature-login`、`ep-user-rebuild`、`fix-2026q2-leak`
|
|
214
220
|
- 禁止包含 `..`、路径分隔符(`/`、`\`)、空格、git 分支禁用字符
|
|
215
221
|
- **需求名同时**作为分支名和 workspace 工作树目录名(`.worktrees/worktree-<需求名>/`)
|
|
216
222
|
|
|
217
|
-
|
|
218
|
-
- 分支名:`feature-login`
|
|
219
|
-
- workspace 工作树目录:`.worktrees/worktree-feature-login`
|
|
220
|
-
- 各应用 worktree 子目录:`.worktrees/worktree-feature-login/<应用名>`
|
|
223
|
+
例:`feature-login` → 分支 `feature-login`;workspace 工作树 `.worktrees/worktree-feature-login/`;应用子目录 `.worktrees/worktree-feature-login/.codespace/<应用名>/`(ADR 0003)
|
|
221
224
|
|
|
222
225
|
### 3.2 安全更新 .gitignore(前置)
|
|
223
226
|
|
|
@@ -265,7 +268,7 @@ node "<skill_dir>/scripts/create-branches.js" "<ws_root>" "<branch>" [--apps "ap
|
|
|
265
268
|
|
|
266
269
|
### 3.4 分支就位(lazy push,不推远端)
|
|
267
270
|
|
|
268
|
-
**本技能不执行任何远端推送(ADR 0002 lazy push)**:分支创建止步于本地。远端副作用收敛到 `cbb-worktree-push` 唯一出口(dry-run 预览 → agent 生成 commit 信息并提交 → push → MR
|
|
271
|
+
**本技能不执行任何远端推送(ADR 0002 lazy push)**:分支创建止步于本地。远端副作用收敛到 `cbb-worktree-push` 唯一出口(dry-run 预览 → agent 生成 commit 信息并提交 → push → Step 7 交付选择:提 MR / 直接合并,ADR 0008)——推送类失败(认证/网络/保护分支)在 push 技能内暴露并处理,不在创建流程中阻塞。
|
|
269
272
|
|
|
270
273
|
> 旧版在本步骤推送空分支建立 upstream(`push-branches.js`),自 0.0.3-beta.22 起移除:避免每个需求在远端留下空分支尸体;`push-worktrees.js` 首次推送时自动建立 upstream,行为等价。
|
|
271
274
|
|
|
@@ -381,7 +384,7 @@ node "<skill_dir>/scripts/junction-ai-config.js" "<ws_root>" "<wt_path>"
|
|
|
381
384
|
| 部分应用 worktree 创建失败 | 提示已创建的回滚清理命令 |
|
|
382
385
|
| workspace 主分支 tracked 内容(openspec/、cbb/ 等) | 自动 checkout 到工作树,无需额外动作(cp/clone 都是多余的) |
|
|
383
386
|
| AI 配置目录(.claude/、.qoder/、.opencode/ 等) | Step 3.6 junction 内容子目录进工作树;唯一物理副本在空间根,`cbb update` 一处生效 |
|
|
384
|
-
| 需求名(同时作分支名和工作树目录名) |
|
|
387
|
+
| 需求名(同时作分支名和工作树目录名) | 对话纯文本输入:直接给名,或给需求描述由 agent 推荐 3–5 个英文候选(`AskUserQuestion` 选定);命名规则见 Step 3.1 |
|
|
385
388
|
| 关联应用范围 | 恒为全量(不询问、不从需求语境收窄);用户显式点名应用时 `--apps` 传子集;增减应用后重跑本技能(幂等) |
|
|
386
389
|
| 单线程快改 / 不需要隔离 | 引导 cbb-local-mode(本地模式,就地切分支) |
|
|
387
390
|
| 运行环境 node 不可用 | 脚本 fail-fast 并提示用户到系统终端重试 |
|
|
@@ -430,7 +433,7 @@ node "<skill_dir>/scripts/junction-ai-config.js" "<ws_root>" "<wt_path>"
|
|
|
430
433
|
### 用 AskUserQuestion 收集自由文本输入
|
|
431
434
|
|
|
432
435
|
- **问题:** 需求名需要用户自由输入文本,却用了 `AskUserQuestion` 工具,被迫提供预设选项
|
|
433
|
-
- **修复:**
|
|
436
|
+
- **修复:** 自由文本(需求名、需求描述)一律**直接在对话中提问**;`AskUserQuestion` 仅用于固定选项——如 Step 3.1 输入描述后的候选名选择
|
|
434
437
|
|
|
435
438
|
### 完成后开发会话未开进 worktree
|
|
436
439
|
|
|
@@ -464,7 +467,7 @@ node "<skill_dir>/scripts/junction-ai-config.js" "<ws_root>" "<wt_path>"
|
|
|
464
467
|
- 跳过或绕过 Step 2 状态检查;状态检查失败后继续
|
|
465
468
|
- workspace 仓库创建工作树前不验证 `.worktrees/` 和 `.codespace/` 在 `.gitignore`
|
|
466
469
|
- 用 `git checkout -b` 切分支后再建 worktree(分支检出冲突)
|
|
467
|
-
- 用 `AskUserQuestion` 收集自由文本(需求名 /
|
|
470
|
+
- 用 `AskUserQuestion` 收集自由文本(需求名 / 需求描述 / 分支选项等)——从固定候选中选名(Step 3.1)除外
|
|
468
471
|
- 运行环境 node 不可用时强行继续 → 改为 fail-fast 提示用户
|
|
469
472
|
- **在本技能内向远端推送任何分支**(lazy push,ADR 0002)——推送只发生在 `cbb-worktree-push`
|
|
470
473
|
- **hook 拦截 main 载荷提交后用 `--no-verify` 绕过**——仅限用户显式要求
|
|
@@ -20,7 +20,7 @@ const HOOK_FILE = 'pre-commit';
|
|
|
20
20
|
const BACKUP_SUFFIX = '.cbb-backup';
|
|
21
21
|
|
|
22
22
|
// 与 cbb/lib/utils/githook.js 的 buildPreCommitSource() 输出完全一致(构建时注入)
|
|
23
|
-
const PRE_COMMIT_SOURCE = "#!/usr/bin/env node\n// managed-by-cbb v1 —— 分支感知提交防线(ADR 0002)\n// main 上:workspace
|
|
23
|
+
const PRE_COMMIT_SOURCE = "#!/usr/bin/env node\n// managed-by-cbb v1 —— 分支感知提交防线(ADR 0002)\n// main 上:workspace 只放行控制面白名单,应用全拦(merge 提交除外);非 main 一律放行。\n// 绕过逃生口仅限用户显式 git commit --no-verify。\nconst { execSync } = require('child_process');\nconst fs = require('fs');\nfunction run(cmd) {\n try { return execSync(cmd, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim(); }\n catch (e) { return null; }\n}\nconst branch = run('git branch --show-current') || '';\n// 主干名:优先远端 HEAD 指向;探测失败按本地 main/master 存在性回退\nlet main = null;\nconst remotes = run('git remote');\nif (remotes) {\n const r = remotes.split(/\\r?\\n/)[0];\n const sym = run('git symbolic-ref \"refs/remotes/' + r + '/HEAD\"');\n if (sym) main = sym.replace('refs/remotes/' + r + '/', '');\n}\nif (!main) {\n if (run('git rev-parse --verify --quiet refs/heads/main')) main = 'main';\n else if (run('git rev-parse --verify --quiet refs/heads/master')) main = 'master';\n}\n// 非主干(含 detached / 探测失败)→ 放行\nif (!main || branch !== main) process.exit(0);\n\n// merge 进行中(MERGE_HEAD 存在)→ 显式人工合并提交,放行(ADR 0007 合并门禁)\nif (run('git rev-parse --verify --quiet MERGE_HEAD')) process.exit(0);\n\n// 主干上的提交:收集 staged 变更(空提交放行)\nconst staged = run('git diff --cached --name-only') || '';\nconst files = staged.split(/\\r?\\n/).filter(Boolean);\nif (files.length === 0) process.exit(0);\n\n// 仓库类型:workspace 仓库 = 检出含 CBB-WORKSPACE.json(或旧名,改名过渡期识别)\nconst isWorkspace = fs.existsSync('CBB-WORKSPACE.json') || fs.existsSync('workspace-config.json');\n\nfunction deny(bad) {\n console.error('[cbb-guard] 主干上拦截载荷提交(ADR 0002):');\n bad.forEach(f => console.error(' ' + f));\n console.error('需求载荷须在需求分支上提交:本地模式先 git checkout -b <需求名>;worktree 模式在 .worktrees/ 工作树内提交。');\n console.error('控制面白名单:CBB-WORKSPACE.json / .gitignore / AGENTS.md / openspec/config.yaml / openspec/schemas/** / .cbb/**');\n console.error('如确需在主干提交,须由用户显式 git commit --no-verify。');\n process.exit(1);\n}\n\nif (isWorkspace) {\n // 旧名仅放行删除条目:更名迁移提交(git mv)会产生旧名删除 + 新名新增两条\n const EXACT = new Set(['CBB-WORKSPACE.json', 'workspace-config.json', '.gitignore', 'AGENTS.md', 'openspec/config.yaml']);\n const PREFIXES = ['openspec/schemas/', '.cbb/'];\n const bad = files.filter(f => !EXACT.has(f) && !PREFIXES.some(p => f.indexOf(p) === 0));\n if (bad.length) deny(bad);\n} else {\n deny(files);\n}\nprocess.exit(0);\n";
|
|
24
24
|
|
|
25
25
|
function isCbbHookContent(content) {
|
|
26
26
|
return typeof content === 'string' && content.includes(HOOK_MARKER);
|
|
@@ -3,7 +3,7 @@ require('./silence-popup');
|
|
|
3
3
|
* openspec-shared 推送核心模块
|
|
4
4
|
*
|
|
5
5
|
* 唯一真源:cbb/worktrees/_shared/scripts/push-core.js
|
|
6
|
-
* 由 scripts/sync-shared.js 展开到各 skill 的 scripts/ 下(init
|
|
6
|
+
* 由 scripts/sync-shared.js 展开到各 skill 的 scripts/ 下(init / push)。
|
|
7
7
|
*
|
|
8
8
|
* 公开 API:
|
|
9
9
|
* - tryExec(cmd, cwd, execImpl) 包装 execSync,统一返回 {ok, out|err}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// koffi is resolved from cbb global install (no local node_modules needed).
|
|
5
5
|
//
|
|
6
6
|
// 唯一真源:cbb/worktrees/_shared/scripts/silence-popup.js
|
|
7
|
-
// 由 scripts/sync-shared.js 展开到各 skill 的 scripts/ 下(init /
|
|
7
|
+
// 由 scripts/sync-shared.js 展开到各 skill 的 scripts/ 下(init / close / push / local-mode)。
|
|
8
8
|
// skill 内同目录引用:脚本第一行 `require('./silence-popup')`。
|
|
9
9
|
if (process.platform === 'win32') {
|
|
10
10
|
try {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: cbb-worktree-push
|
|
3
|
-
description: "提交并推送当前需求的所有关联仓库(跨 worktree
|
|
3
|
+
description: "提交并推送当前需求的所有关联仓库(跨 worktree 批量),推送后提供交付选择:提 MR(预填链接)或由 agent 直接合并到目标分支(主干/测试分支,ADR 0008)。触发:用户说'提交'/'commit'/'push'/'推送'/'提交并push'/'推到远端'/'提MR'/'提合并请求'/'merge request';用户在 .worktrees/worktree-<需求名>/ 下工作要求批量推送;apply 完成后准备推送所有 worktree;close-worktree 前需要先推送未提交改动。注意:本技能会先识别场景——不在 cbb 工作空间时降级为普通单仓库提交推送。"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Push Worktrees(跨平台脚本化版本 v2.0)
|
|
@@ -15,7 +15,7 @@ description: "提交并推送当前需求的所有关联仓库(跨 worktree
|
|
|
15
15
|
|
|
16
16
|
- 本技能的所有原子操作封装为 `scripts/*.js` Node.js 脚本,**跨平台执行**
|
|
17
17
|
- **两阶段**:先 `commit-worktrees.js` 提交所有 dirty 仓库,再 `push-worktrees.js` 推送所有 worktree 仓库。两个脚本独立退出码,AI agent 区分阶段
|
|
18
|
-
- **push
|
|
18
|
+
- **push 后交付选择(Step 7,ADR 0008)**:三选一——提 MR(`make-mr-urls.js` 生成预填链接,可自动开浏览器;只生成链接,提交/评审/合并由人在平台完成,不触碰凭据)/ **直接合并**(`merge-branches.js` 把需求分支合入目标分支并推送;需经归档前置、提交检查、目标选择与风险确认)/ 暂不处理
|
|
19
19
|
- 复用 `cbb-worktree-push/scripts/push-core.js` 的 `pushSingle` / `classifyError` —— 与 `cbb-worktree-init` 的 `push-branches.js` 行为完全一致,错误码契约统一
|
|
20
20
|
- `find-workspace-root.js` / `find-target-worktree.js` 为共享脚本:源仓库真源在 `cbb/worktrees/_shared/scripts/`,部署后位于本 skill 的 `scripts/` 下,与 `cbb-worktree-init` / `cbb-worktree-close` 逻辑完全一致
|
|
21
21
|
- **本技能支持两层执行位置**(与 `cbb-worktree-close` 对称):
|
|
@@ -216,21 +216,25 @@ node "<skill_dir>/scripts/push-worktrees.js" "<ws_root>/.worktrees/worktree-<bra
|
|
|
216
216
|
|
|
217
217
|
---
|
|
218
218
|
|
|
219
|
-
## Step 7
|
|
219
|
+
## Step 7:交付选择(MR / 直接合并 / 暂不处理)
|
|
220
220
|
|
|
221
|
-
Step 5 的 `PUSH_RESULT` 中至少一个仓库为 `pushed` / `skipped`(feature 分支已在远端)时,用 `AskUserQuestion
|
|
221
|
+
Step 5 的 `PUSH_RESULT` 中至少一个仓库为 `pushed` / `skipped`(feature 分支已在远端)时,用 `AskUserQuestion` 提案。**动作与目标解耦**(ADR 0008):
|
|
222
222
|
|
|
223
223
|
```
|
|
224
|
-
|
|
225
|
-
[提 MR
|
|
226
|
-
[
|
|
227
|
-
[
|
|
224
|
+
推送完成。接下来怎么交付?
|
|
225
|
+
[提 MR](推荐) 生成预填的合并请求链接,由人工在浏览器合入
|
|
226
|
+
[直接合并] 由 agent 直接合并到目标分支并推送远端(不经过 MR)
|
|
227
|
+
[暂不处理] 结束本步,分支已推到远端
|
|
228
228
|
```
|
|
229
229
|
|
|
230
|
-
|
|
230
|
+
### 7.1 目标分支候选(A / B 两出口共用)
|
|
231
231
|
|
|
232
|
-
|
|
233
|
-
|
|
232
|
+
`git branch -r` 列出**现场实际存在**的候选(主干 / dev / test / release-* 等),交互式让用户指定(**禁止 AI 代决**)。全仓库统一用一个目标分支;远端不存在该分支的仓库自动跳过。
|
|
233
|
+
|
|
234
|
+
### 7.2 出口 A · 提 MR
|
|
235
|
+
|
|
236
|
+
1. 确定目标分支(见 7.1)
|
|
237
|
+
2. 生成链接:
|
|
234
238
|
|
|
235
239
|
```bash
|
|
236
240
|
node "<skill_dir>/scripts/make-mr-urls.js" "<ws_root>" "<branch>" "<target>" --open
|
|
@@ -245,7 +249,117 @@ node "<skill_dir>/scripts/make-mr-urls.js" "<ws_root>" "<branch>" "<target>" --o
|
|
|
245
249
|
|
|
246
250
|
> 平台识别:`github.com` → PR compare、`gitee.com` → compare、**其余默认 GitLab**(企业自建主流);误判用 `--platform` 覆盖。
|
|
247
251
|
|
|
248
|
-
3. **提 MR 后提醒用户后续链路**:测试环境部署 → 用户验收 → 验收通过后 `/opsx:archive` → 正式分支合并走公司发布流程 → 最后 `cbb-worktree-close
|
|
252
|
+
3. **提 MR 后提醒用户后续链路**:测试环境部署 → 用户验收 → 验收通过后 `/opsx:archive` → 正式分支合并走公司发布流程 → 最后 `cbb-worktree-close`(其收尾合并门禁会校验主干合并状态,未合并且非废弃不放行;见 ADR 0007)
|
|
253
|
+
|
|
254
|
+
### 7.3 出口 B · 直接合并(ADR 0008)
|
|
255
|
+
|
|
256
|
+
**前置顺序:先归档(仅目标 = 主干)、后提交检查;两步都满足才可合并。**
|
|
257
|
+
|
|
258
|
+
#### B1 归档前置(仅当目标 = 主干)
|
|
259
|
+
|
|
260
|
+
目标为主干时**合并前必须已归档**;检查(共享脚本):
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
node "<skill_dir>/scripts/check-unarchived.js" "<ws_root>" "<branch>"
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
- 末行 `UNARCHIVED_RESULT` 的 `changes` 为空 → 通过,进 B2
|
|
267
|
+
- 非空 → `AskUserQuestion`:
|
|
268
|
+
|
|
269
|
+
```
|
|
270
|
+
⚠️ 检测到未归档 change:<名称>
|
|
271
|
+
合并进主干前必须已归档(/opsx:archive)。
|
|
272
|
+
|
|
273
|
+
[去归档](推荐) 立即执行归档流程;归档产物落盘后自动回到本步
|
|
274
|
+
[改为提 MR] 转出口 A
|
|
275
|
+
[中止] 结束本步
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
选「去归档」→ agent 执行归档流程(`/opsx:archive`,产物落 `openspec/changes/archive/`)→ 进入 B2(归档产物属未提交改动,由 B2 处理)。
|
|
279
|
+
|
|
280
|
+
> 目标为测试 / 中间分支时**不要求归档**(归档仍在验收通过后执行);「已提交」检查两类目标都适用。
|
|
281
|
+
|
|
282
|
+
#### B2 提交检查(通用)
|
|
283
|
+
|
|
284
|
+
逐仓库 `git status --porcelain`。存在未提交改动(含归档产物)时:
|
|
285
|
+
|
|
286
|
+
```
|
|
287
|
+
⚠️ 检测到未提交改动:
|
|
288
|
+
- workspace: 2 个文件
|
|
289
|
+
|
|
290
|
+
是否提交并推送后再继续合并?
|
|
291
|
+
[提交并推送](推荐) 走提交推送流程,完成后自动回到合并
|
|
292
|
+
[中止] 我先手动处理
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
- 选「提交并推送」→ 执行本技能 Step 2 → Step 5(dry-run 预览 → 批量 commit → 批量 push)→ **直接回到 B3**(不再重复动作选择,避免递归)
|
|
296
|
+
- 选「中止」→ 结束本步(**前置不满足,不予合并**)
|
|
297
|
+
|
|
298
|
+
#### B3 目标分支确认
|
|
299
|
+
|
|
300
|
+
从 7.1 的候选里选定,明确显示主干项:
|
|
301
|
+
|
|
302
|
+
```
|
|
303
|
+
请选择直接合并的目标分支:
|
|
304
|
+
[主干 main] 合并进正式主干(跳过测试与验收)
|
|
305
|
+
[dev] / [test] / … 合并进中间分支(验收后主干仍需另行交付)
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
#### B4 风险提醒 + 确认(必须显式确认)
|
|
309
|
+
|
|
310
|
+
目标 = 主干(强提醒):
|
|
311
|
+
|
|
312
|
+
```
|
|
313
|
+
⚠️ 你选择直接合并到主干 <main> —— 将跳过:
|
|
314
|
+
• 测试环境部署与用户验收
|
|
315
|
+
• 公司正式发布流程
|
|
316
|
+
• MR 评审记录(平台无评审痕迹)
|
|
317
|
+
|
|
318
|
+
合并后内容立即进入主干并推送远端,撤回只能靠 revert 补救。
|
|
319
|
+
|
|
320
|
+
[确认合并] 我已知晓,执行合并
|
|
321
|
+
[改为提 MR] 转出口 A
|
|
322
|
+
[中止] 结束本步
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
目标 = 测试 / 中间分支(弱提醒):
|
|
326
|
+
|
|
327
|
+
```
|
|
328
|
+
⚠️ 你选择直接合并到 <dev> —— 将跳过 MR 评审(平台无记录);
|
|
329
|
+
验收通过后主干仍需另行交付。
|
|
330
|
+
|
|
331
|
+
[确认合并] / [改为提 MR] / [中止]
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
#### B5 执行与报告
|
|
335
|
+
|
|
336
|
+
```bash
|
|
337
|
+
node "<skill_dir>/scripts/merge-branches.js" "<ws_root>" "<branch>" --apps "<app1,app2>" --target "<目标>"
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
- 缺省 / `--target` 等于探测主干 → 主干快路径(本地主干检出上 `merge --no-ff` → push)
|
|
341
|
+
- `--target` 为非主干分支 → **临时 worktree** 合并(基于 `origin/<目标>`,不动用户检出)
|
|
342
|
+
|
|
343
|
+
**解读末行 `MERGE_RESULT`**:
|
|
344
|
+
- 退出码 0 → 交付完成
|
|
345
|
+
- `need-mr`(目标分支受保护)→ 报明细并**转出口 A**
|
|
346
|
+
- `failed`(含 `merge-failed` / `push-failed`)→ 报明细,修复后重跑(幂等)
|
|
347
|
+
- `not-on-main`(仅主干路径)→ 空间根检出被其他需求占用(本地模式进行中):**先收尾该本地需求或改用出口 A 提 MR**;不得用 `--allow-checkout` 抢占(该参数属本地模式收尾,会切走占用者的工作区)
|
|
348
|
+
|
|
349
|
+
报告形态:
|
|
350
|
+
|
|
351
|
+
```
|
|
352
|
+
✅ 直接合并完成
|
|
353
|
+
目标分支:main
|
|
354
|
+
workspace: merged(merge --no-ff + push origin/main)
|
|
355
|
+
jspg-cvat: merged
|
|
356
|
+
jspg-cvat-model-factory: skipped(远端无该分支)
|
|
357
|
+
|
|
358
|
+
后续:主干已交付 → cbb-worktree-close 收尾将直接放行并自动删分支;
|
|
359
|
+
中间分支已交付(dev)→ 验收通过后主干仍需另行交付
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
> **直接合并成功后不删分支**(ADR 0008):分支与 worktree 保留(验收可能需修复),删除由收尾门禁(`cbb-worktree-close`)统一执行。
|
|
249
363
|
|
|
250
364
|
---
|
|
251
365
|
|
|
@@ -263,7 +377,7 @@ node "<skill_dir>/scripts/make-mr-urls.js" "<ws_root>" "<branch>" "<target>" --o
|
|
|
263
377
|
workspace: ✅ origin/<branch>
|
|
264
378
|
ep-foo-app: ✅ origin/<branch>
|
|
265
379
|
ep-bar-app: ⚠️ 远程分支已存在(需用户决策 → 已关联)
|
|
266
|
-
后续:Step 7
|
|
380
|
+
后续:Step 7 交付选择(提 MR 到测试分支 → 部署验收 → /opsx:archive → 正式发布;或直接合并到主干/测试分支,ADR 0008)→ 最后 cbb-worktree-close
|
|
267
381
|
```
|
|
268
382
|
|
|
269
383
|
---
|
|
@@ -285,7 +399,9 @@ node "<skill_dir>/scripts/make-mr-urls.js" "<ws_root>" "<branch>" "<target>" --o
|
|
|
285
399
|
| 远程分支已存在 | push 退出码 2,AI 输出元信息后由用户决策(关联/终止) |
|
|
286
400
|
| push 失败(SSH/HTTPS/网络/保护) | push 退出码 2,AI 按错误码分类处理,阻塞并询问用户 |
|
|
287
401
|
| close-worktree 前调用 | 先调本 skill 把 ahead 推送完,避免 close 时丢提交 |
|
|
288
|
-
| push 完成后 | Step 7 MR
|
|
402
|
+
| push 完成后 | Step 7 交付选择:提 MR → make-mr-urls.js 生成预填链接 + 开浏览器(不代合并);直接合并 → B1 归档(仅主干)→ B2 提交检查 → B3 目标 → B4 风险确认 → merge-branches.js 执行;暂不处理 |
|
|
403
|
+
| 直合并前置 | 目标=主干必须先归档(check-unarchived.js);有未提交改动则询问后走本技能提交推送流程;未确认不合并 |
|
|
404
|
+
| 直合并后 | **不删分支**(ADR 0008),删除归 cbb-worktree-close 收尾门禁 |
|
|
289
405
|
| MR 目标分支 | 交互式:`git branch -r` 列主干候选由用户指定,全仓库统一;缺该分支的仓库自动跳过(no-target) |
|
|
290
406
|
| MR 平台误判 | 非 github.com/gitee.com 默认按 GitLab 拼 URL;有误判用 `--platform gitlab\|github\|gitee` 强制 |
|
|
291
407
|
| --dry-run | 任何时候想预览将要 commit 的内容时使用,不实际修改任何东西 |
|
|
@@ -311,13 +427,13 @@ node "<skill_dir>/scripts/make-mr-urls.js" "<ws_root>" "<branch>" "<target>" --o
|
|
|
311
427
|
|
|
312
428
|
### close-worktree 前忘记先 push
|
|
313
429
|
|
|
314
|
-
- **问题:** 调 close-worktree 时,
|
|
315
|
-
- **修复:** close-worktree/SKILL.md Step 3.
|
|
430
|
+
- **问题:** 调 close-worktree 时,safety-check 检测到 ahead 提交 → 询问用户「先推送 / 继续 / 中止」;用户没意识到要调本 skill
|
|
431
|
+
- **修复:** close-worktree/SKILL.md Step 3.3 决策点已提示「先推送」选项;本 skill 是该选项的执行入口。走「直合并」出口时,ahead 提交会随合并一并推送到主干
|
|
316
432
|
|
|
317
433
|
### apply 完成后不调 push 直接 close
|
|
318
434
|
|
|
319
|
-
- **问题:** apply 实现完 → 用户关掉 IDE → 调 close-worktree →
|
|
320
|
-
- **修复:**
|
|
435
|
+
- **问题:** apply 实现完 → 用户关掉 IDE → 调 close-worktree → 收尾合并门禁触发(ADR 0007),未合并且非废弃不放行
|
|
436
|
+
- **修复:** 走门禁三选一:提 MR(先经本 skill 推送 feature)或直合并(ahead 提交随合并推送到主干)
|
|
321
437
|
|
|
322
438
|
### 多仓库硬塞同一条 commit 信息
|
|
323
439
|
|
|
@@ -346,7 +462,10 @@ node "<skill_dir>/scripts/make-mr-urls.js" "<ws_root>" "<branch>" "<target>" --o
|
|
|
346
462
|
- 远程分支已存在时脚本自动 set-upstream-to
|
|
347
463
|
- AI 询问关联选项前不输出 commit 元信息
|
|
348
464
|
- 运行环境 node 不可用时强行继续 → 改为 fail-fast 提示用户
|
|
349
|
-
-
|
|
465
|
+
- MR 出口下代为合并 / 触碰平台凭据 → MR 出口只生成预填链接,提交与合并由人在平台完成。**直合并只能经 Step 7 出口 B 的 `merge-branches.js` 执行(ADR 0008),且必须先过 B1 归档前置、B2 提交检查、B4 风险确认**
|
|
466
|
+
- 直合并跳过风险提醒直接执行 → 目标为主干时必须强提醒并显式确认;**未经确认不得合并**
|
|
467
|
+
- 目标为主干但未归档就合并 → 必须先归档(B1)
|
|
468
|
+
- 直合并成功后擅自删除分支 → 分支保留,删除归收尾门禁(ADR 0007)
|
|
350
469
|
- MR 目标分支由 AI 猜测代决 → 必须列候选让用户指定
|
|
351
470
|
|
|
352
471
|
**必须执行**
|
|
@@ -360,7 +479,9 @@ node "<skill_dir>/scripts/make-mr-urls.js" "<ws_root>" "<branch>" "<target>" --o
|
|
|
360
479
|
- 多个仓库改动语义不同时必须逐仓库分别生成(`--per-repo`),禁止硬塞同一条
|
|
361
480
|
- commit-worktrees / push-worktrees 退出码 2 时必须阻塞流程
|
|
362
481
|
- 远程分支已存在时 AI 必须先输出 commit 元信息再询问
|
|
363
|
-
- push 成功后必须走 Step 7
|
|
482
|
+
- push 成功后必须走 Step 7 交付选择(AskUserQuestion 固定选项;用户选"暂不处理"则正常结束)
|
|
483
|
+
- 直合并严格按 B1(归档,仅主干)→ B2(提交检查)→ B3(目标)→ B4(确认)→ B5(执行)顺序;前置不满足不予合并
|
|
484
|
+
- 直合并目标为中间分支时跳过 B1(不要求归档);两类目标都必须过 B2
|
|
364
485
|
|
|
365
486
|
---
|
|
366
487
|
|
|
@@ -374,3 +495,5 @@ node "<skill_dir>/scripts/make-mr-urls.js" "<ws_root>" "<branch>" "<target>" --o
|
|
|
374
495
|
| `push-worktrees.js` | 批量 push(复用 push-core 错误归类) | ws_root, branch(或 wt_path) | 0/1/2 |
|
|
375
496
|
| `make-mr-urls.js` | 生成预填 MR/PR 创建链接(平台识别 + 远端校验 + diff 判定 + `--open` 开浏览器) | ws_root, branch, target(或 wt_path, target), --platform, --open | 0/1/2 |
|
|
376
497
|
| `push-core.js` | 推送核心(classifyError / pushSingle / collectRemoteMetadata,共享脚本,源仓库真源在 `_shared/scripts/`) | (共享模块) | - |
|
|
498
|
+
| `check-unarchived.js` | 未归档 change 检查(Step 7 直合并的归档前置,共享脚本) | ws_root, branch | 0/1 |
|
|
499
|
+
| `merge-branches.js` | 直合并(Step 7 出口 B / 收尾门禁;预检 → merge --no-ff → push;`--target` 支持测试分支,共享脚本) | ws_root, branch, --apps / --target / --allow-checkout / --dry-run / --check | 0/1/2 |
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
require('./silence-popup');
|
|
3
|
+
/**
|
|
4
|
+
* 未归档 change 检查(共享脚本,真源:cbb/worktrees/_shared/scripts/)
|
|
5
|
+
*
|
|
6
|
+
* 供 cbb-worktree-close(收尾安全评估)与 cbb-worktree-push(Step 7 直合并前置,ADR 0008)调用。
|
|
7
|
+
*
|
|
8
|
+
* 用法:node check-unarchived.js <ws_root> [branch]
|
|
9
|
+
*
|
|
10
|
+
* 双扫(ADR 0002):
|
|
11
|
+
* - <ws_root>/openspec/changes/ → 空间根当前检出(本地模式产物 / 空间根遗留)
|
|
12
|
+
* - <ws_root>/.worktrees/worktree-<branch>/openspec/changes/ → worktree 模式产物
|
|
13
|
+
* (branch 未传时跳过 worktree 扫描,保持旧调用兼容)
|
|
14
|
+
*
|
|
15
|
+
* 输出:changes 为两处合集(去重);detail 标注每个 change 的来源
|
|
16
|
+
* ('root' / 'worktree',同名两处共存时为 'both')。
|
|
17
|
+
* 本检查为信息收集性质,不阻断流程。
|
|
18
|
+
*
|
|
19
|
+
* 退出码:
|
|
20
|
+
* 0 - 检查完成(无论是否有未归档 change)
|
|
21
|
+
* 1 - 参数错误
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const fs = require('fs');
|
|
25
|
+
const path = require('path');
|
|
26
|
+
|
|
27
|
+
const wsRoot = process.argv[2];
|
|
28
|
+
const branch = process.argv[3];
|
|
29
|
+
|
|
30
|
+
if (!wsRoot) {
|
|
31
|
+
console.error('Usage: node check-unarchived.js <ws_root> [branch]');
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 扫描单个 changes 目录,返回未归档 change 名数组(目录不存在返回 [])。
|
|
37
|
+
*/
|
|
38
|
+
function scanDir(changesDir) {
|
|
39
|
+
if (!fs.existsSync(changesDir)) return [];
|
|
40
|
+
let entries;
|
|
41
|
+
try {
|
|
42
|
+
entries = fs.readdirSync(changesDir, { withFileTypes: true });
|
|
43
|
+
} catch (e) {
|
|
44
|
+
console.error('Failed to read ' + changesDir + ':', e.message);
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
return entries
|
|
48
|
+
.filter(e => e.isDirectory() && e.name !== 'archive')
|
|
49
|
+
.map(e => e.name);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const rootChanges = scanDir(path.join(wsRoot, 'openspec', 'changes'));
|
|
53
|
+
|
|
54
|
+
const worktreeDir = branch
|
|
55
|
+
? path.join(wsRoot, '.worktrees', 'worktree-' + branch, 'openspec', 'changes')
|
|
56
|
+
: null;
|
|
57
|
+
const wtChanges = worktreeDir ? scanDir(worktreeDir) : [];
|
|
58
|
+
|
|
59
|
+
const detail = {};
|
|
60
|
+
for (const name of rootChanges) detail[name] = 'root';
|
|
61
|
+
for (const name of wtChanges) {
|
|
62
|
+
detail[name] = detail[name] === 'root' ? 'both' : 'worktree';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const changes = Object.keys(detail).sort();
|
|
66
|
+
|
|
67
|
+
if (rootChanges.length === 0 && wtChanges.length === 0) {
|
|
68
|
+
console.log('ℹ️ 未发现未归档 change(根与 worktree' + (worktreeDir ? '' : '(未指定 branch,跳过)') + '均无)');
|
|
69
|
+
} else {
|
|
70
|
+
console.log('ℹ️ 未归档 change:' + (changes.join(', ') || '无'));
|
|
71
|
+
for (const name of changes) {
|
|
72
|
+
console.log(' - ' + name + '(来源:' + detail[name] + ')');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
console.log('UNARCHIVED_RESULT', JSON.stringify({ changes, detail }));
|
|
77
|
+
process.exit(0);
|
|
@@ -3,7 +3,7 @@ require('./silence-popup');
|
|
|
3
3
|
* openspec-shared 目标需求名识别
|
|
4
4
|
*
|
|
5
5
|
* 唯一真源:cbb/worktrees/_shared/scripts/find-target-worktree.js
|
|
6
|
-
* 由 scripts/sync-shared.js 展开到各 skill 的 scripts/ 下(
|
|
6
|
+
* 由 scripts/sync-shared.js 展开到各 skill 的 scripts/ 下(close / push)。
|
|
7
7
|
*
|
|
8
8
|
* 用法:node find-target-worktree.js <ws_root>
|
|
9
9
|
*
|
|
@@ -3,7 +3,7 @@ require('./silence-popup');
|
|
|
3
3
|
* openspec-shared 工作空间根目录定位
|
|
4
4
|
*
|
|
5
5
|
* 唯一真源:cbb/worktrees/_shared/scripts/find-workspace-root.js
|
|
6
|
-
* 由 scripts/sync-shared.js 展开到各 skill 的 scripts/ 下(
|
|
6
|
+
* 由 scripts/sync-shared.js 展开到各 skill 的 scripts/ 下(close / push)。
|
|
7
7
|
*
|
|
8
8
|
* 优先:git worktree list 第一行(主仓库根目录,不受 worktree 内 CBB-WORKSPACE.json 副本干扰)
|
|
9
9
|
* 兜底:当前目录含 CBB-WORKSPACE.json
|