@jspg-ai/coding-bb 0.0.3-beta.8 → 0.0.4
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 +0 -15
- package/cbb/dev-standards/rules/cbb-workspace-discipline.md +38 -0
- package/cbb/lib/external/index.js +228 -0
- package/cbb/lib/install/agents-nav.js +92 -0
- package/cbb/lib/install/claude-code.js +8 -1
- package/cbb/lib/install/command-skill.js +105 -0
- package/cbb/lib/install/init.js +399 -129
- package/cbb/lib/install/minimax.js +60 -0
- package/cbb/lib/install/rule-rewrite.js +1 -1
- package/cbb/lib/install/setup-decision.js +92 -0
- package/cbb/lib/install/tool-selection.js +23 -0
- package/cbb/lib/install/trae.js +18 -8
- package/cbb/lib/install/workbuddy.js +36 -0
- package/cbb/lib/install/workspaces.js +15 -61
- package/cbb/lib/utils/check-update.js +13 -5
- package/cbb/lib/utils/githook.js +230 -0
- package/cbb/lib/utils/gitignore.js +17 -4
- package/cbb/lib/utils/plugin-hook.js +92 -0
- package/cbb/worktrees/_shared/scripts/check-local-state.js +114 -0
- 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 +148 -96
- package/cbb/worktrees/_shared/scripts/find-workspace-root.js +8 -8
- package/cbb/worktrees/_shared/scripts/install-git-hook.js +70 -0
- package/cbb/worktrees/_shared/scripts/junction-ai-config.js +192 -0
- 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/_shared/scripts/start-local.js +180 -0
- package/cbb/worktrees/skills/cbb-local-mode/SKILL.md +194 -0
- package/cbb/worktrees/skills/cbb-local-mode/scripts/check-local-state.js +114 -0
- 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 +70 -0
- package/cbb/worktrees/skills/cbb-local-mode/scripts/merge-branches.js +469 -0
- package/cbb/worktrees/skills/cbb-local-mode/scripts/silence-popup.js +23 -0
- package/cbb/worktrees/skills/cbb-local-mode/scripts/start-local.js +180 -0
- package/cbb/worktrees/skills/cbb-worktree-close/SKILL.md +133 -81
- package/cbb/worktrees/skills/cbb-worktree-close/scripts/check-env.js +3 -3
- package/cbb/worktrees/skills/cbb-worktree-close/scripts/check-unarchived.js +77 -48
- 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/discover-apps.js +14 -5
- package/cbb/worktrees/skills/cbb-worktree-close/scripts/find-target-worktree.js +148 -96
- package/cbb/worktrees/skills/cbb-worktree-close/scripts/find-workspace-root.js +8 -8
- package/cbb/worktrees/skills/cbb-worktree-close/scripts/junction-ai-config.js +192 -0
- package/cbb/worktrees/skills/cbb-worktree-close/scripts/merge-branches.js +469 -0
- package/cbb/worktrees/skills/cbb-worktree-close/scripts/remove-worktrees.js +44 -9
- package/cbb/worktrees/skills/cbb-worktree-close/scripts/safety-check.js +15 -4
- package/cbb/worktrees/skills/cbb-worktree-close/scripts/silence-popup.js +1 -1
- package/cbb/worktrees/skills/cbb-worktree-init/SKILL.md +136 -123
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/check-env.js +1 -1
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/check-repos.js +2 -2
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/create-branches.js +33 -8
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/create-worktrees.js +60 -46
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/install-git-hook.js +70 -0
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/junction-ai-config.js +192 -0
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/parse-config.js +7 -7
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/push-branches.js +3 -3
- 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-init/scripts/sync-repos.js +11 -7
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/update-gitignore.js +37 -9
- package/cbb/worktrees/skills/cbb-worktree-push/SKILL.md +212 -56
- package/cbb/worktrees/skills/cbb-worktree-push/scripts/check-unarchived.js +77 -0
- package/cbb/worktrees/skills/cbb-worktree-push/scripts/commit-worktrees.js +70 -12
- package/cbb/worktrees/skills/cbb-worktree-push/scripts/find-target-worktree.js +148 -96
- package/cbb/worktrees/skills/cbb-worktree-push/scripts/find-workspace-root.js +8 -8
- package/cbb/worktrees/skills/cbb-worktree-push/scripts/make-mr-urls.js +211 -0
- 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/push-worktrees.js +67 -20
- package/cbb/worktrees/skills/cbb-worktree-push/scripts/silence-popup.js +1 -1
- package/config/{workspace-config.sample.json → CBB-WORKSPACE.sample.json} +1 -1
- package/config/openspec/schemas/spec-driven/schema.yaml +20 -0
- package/config/upstream-mirrors.json +5 -0
- package/external/frontend-design/.version +6 -0
- package/external/frontend-design/LICENSE.txt +177 -0
- package/external/frontend-design/SKILL.md +71 -0
- package/openspec/.version +2 -2
- package/openspec/commands/archive.md +15 -8
- package/openspec/commands/bulk-archive.md +13 -6
- package/openspec/commands/continue.md +0 -1
- package/openspec/commands/explore.md +1 -1
- package/openspec/commands/ff.md +1 -1
- package/openspec/commands/onboard.md +9 -4
- package/openspec/commands/update.md +11 -5
- package/openspec/commands/verify.md +57 -26
- package/openspec/skills/openspec-apply-change/SKILL.md +1 -1
- package/openspec/skills/openspec-archive-change/SKILL.md +16 -9
- package/openspec/skills/openspec-bulk-archive-change/SKILL.md +14 -7
- package/openspec/skills/openspec-continue-change/SKILL.md +1 -2
- package/openspec/skills/openspec-explore/SKILL.md +2 -2
- package/openspec/skills/openspec-ff-change/SKILL.md +2 -2
- package/openspec/skills/openspec-new-change/SKILL.md +1 -1
- package/openspec/skills/openspec-onboard/SKILL.md +10 -5
- package/openspec/skills/openspec-propose/SKILL.md +1 -1
- package/openspec/skills/openspec-sync-specs/SKILL.md +1 -1
- package/openspec/skills/openspec-update-change/SKILL.md +12 -6
- package/openspec/skills/openspec-verify-change/SKILL.md +59 -27
- package/package.json +3 -2
- package/superpowers/.version +3 -3
- package/superpowers/skills/writing-plans/SKILL.md +32 -20
- package/cbb/lib/install/codebuddy.js +0 -30
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/find-workspace-root.js +0 -67
- package/config/workspace-agents.sample.md +0 -41
- package/superpowers/skills/writing-plans/plan-document-reviewer-prompt.md +0 -49
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 本地模式开工(ADR 0002):根 + 指定应用就地切需求分支。
|
|
4
|
+
*
|
|
5
|
+
* 用法:node start-local.js <ws_root> <branch> [--apps app1,app2] [--all-apps]
|
|
6
|
+
*
|
|
7
|
+
* 行为:
|
|
8
|
+
* 1. 前置自检:根与所有 .codespace/<app>/ 若任一检出在非主干分支 → 遗留态,
|
|
9
|
+
* 退出码 2(agent 应升级为用户决策:继续 / 收尾 / 升级 / 丢弃,不在本脚本内处理)
|
|
10
|
+
* 2. 参与仓库 = workspace + 应用子集:
|
|
11
|
+
* - --apps a,b → 指定应用(本地模式推荐:从需求语境提取涉及应用)
|
|
12
|
+
* - --all-apps → CBB-WORKSPACE.json 全量应用
|
|
13
|
+
* - 缺省 → 仅 workspace(后续重跑幂等补齐,与 worktree-init 增补语义一致)
|
|
14
|
+
* 3. 每个仓库:
|
|
15
|
+
* - 当前已在 <branch> → reused
|
|
16
|
+
* - 分支存在(未检出)→ git checkout
|
|
17
|
+
* - 否则 → git checkout -b <branch> <remote>/<main>(基线不取 HEAD,ADR 0002;
|
|
18
|
+
* remote 不可用回退本地 main/master,再回退 HEAD)
|
|
19
|
+
* - dirty 仓库切换失败 → 报错退出(不 stash 兜底,交给用户决策)
|
|
20
|
+
* 4. 参与的应用仓库幂等补装提交防线 hook(cbb setup 已装,此处兜底)
|
|
21
|
+
*
|
|
22
|
+
* 幂等:重跑同一 branch 安全(reused / checkout 跳过)。
|
|
23
|
+
*
|
|
24
|
+
* 退出码:
|
|
25
|
+
* 0 - 全部切换成功
|
|
26
|
+
* 1 - 参数错误
|
|
27
|
+
* 2 - 遗留态 / 部分切换失败
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
const { execSync } = require('child_process');
|
|
31
|
+
const fs = require('fs');
|
|
32
|
+
const path = require('path');
|
|
33
|
+
const { installTo: installGitHook } = require('./install-git-hook');
|
|
34
|
+
|
|
35
|
+
const wsRoot = process.argv[2];
|
|
36
|
+
const branch = process.argv[3];
|
|
37
|
+
|
|
38
|
+
if (!wsRoot || !branch) {
|
|
39
|
+
console.error('Usage: node start-local.js <ws_root> <branch> [--apps a,b] [--all-apps]');
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function run(cmd, cwd) {
|
|
44
|
+
try {
|
|
45
|
+
return { ok: true, out: execSync(cmd, { cwd, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] }).toString().trim() };
|
|
46
|
+
} catch (e) {
|
|
47
|
+
return { ok: false, err: (e.stderr ? e.stderr.toString() : e.message || String(e)).trim() };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** 主干名:symbolic-ref 远端 HEAD → 本地 main/master 回退 */
|
|
52
|
+
function detectMain(cwd) {
|
|
53
|
+
const remotes = run('git remote', cwd);
|
|
54
|
+
if (remotes.ok && remotes.out) {
|
|
55
|
+
const r = remotes.out.split(/\r?\n/)[0];
|
|
56
|
+
const sym = run('git symbolic-ref "refs/remotes/' + r + '/HEAD"', cwd);
|
|
57
|
+
if (sym.ok && sym.out) return { remote: r, main: sym.out.replace('refs/remotes/' + r + '/', '') };
|
|
58
|
+
}
|
|
59
|
+
if (run('git rev-parse --verify --quiet refs/heads/main', cwd).ok) return { remote: null, main: 'main' };
|
|
60
|
+
if (run('git rev-parse --verify --quiet refs/heads/master', cwd).ok) return { remote: null, main: 'master' };
|
|
61
|
+
return { remote: null, main: null };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function currentBranch(cwd) {
|
|
65
|
+
const r = run('git branch --show-current', cwd);
|
|
66
|
+
return r.ok ? r.out : '';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ── 1. 前置自检:遗留态检测 ───────────────────────────────
|
|
70
|
+
// 「非主干」且「不是本次 branch」才是遗留(别的需求没收尾);
|
|
71
|
+
// 已检出在本次 branch 上 = 幂等补齐场景,放行(reused)。
|
|
72
|
+
|
|
73
|
+
function isOnMain(cwd) {
|
|
74
|
+
const info = detectMain(cwd);
|
|
75
|
+
return info.main ? currentBranch(cwd) === info.main : false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function isOnTarget(cwd) {
|
|
79
|
+
return currentBranch(cwd) === branch || isOnMain(cwd);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const leftovers = [];
|
|
83
|
+
if (!isOnTarget(wsRoot)) leftovers.push({ name: 'workspace', branch: currentBranch(wsRoot) });
|
|
84
|
+
const codeDir = path.join(wsRoot, '.codespace');
|
|
85
|
+
if (fs.existsSync(codeDir)) {
|
|
86
|
+
for (const name of fs.readdirSync(codeDir).sort()) {
|
|
87
|
+
const appDir = path.join(codeDir, name);
|
|
88
|
+
if (fs.existsSync(path.join(appDir, '.git')) && !isOnTarget(appDir)) {
|
|
89
|
+
leftovers.push({ name, branch: currentBranch(appDir) });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (leftovers.length > 0) {
|
|
94
|
+
console.error('❌ 遗留态:以下仓库检出在非主干分支,先由用户决策处理(继续/收尾/升级/丢弃):');
|
|
95
|
+
leftovers.forEach(l => console.error(' - ' + l.name + ': ' + l.branch));
|
|
96
|
+
console.log('---');
|
|
97
|
+
console.log('LOCAL_START_FAILED', JSON.stringify({ reason: 'leftover', leftovers }));
|
|
98
|
+
process.exit(2);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ── 2. 参与仓库集合 ───────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
let appNames = [];
|
|
104
|
+
const argApps = process.argv.indexOf('--apps');
|
|
105
|
+
const argAll = process.argv.includes('--all-apps');
|
|
106
|
+
if (argApps >= 0) {
|
|
107
|
+
const raw = process.argv[argApps + 1] || '';
|
|
108
|
+
appNames = raw.split(',').map(s => s.trim()).filter(Boolean);
|
|
109
|
+
} else if (argAll) {
|
|
110
|
+
try {
|
|
111
|
+
const config = JSON.parse(fs.readFileSync(path.join(wsRoot, 'CBB-WORKSPACE.json'), 'utf-8'));
|
|
112
|
+
appNames = (config.apps || []).map(a => a.name);
|
|
113
|
+
} catch (e) {
|
|
114
|
+
console.error('❌ 读取 CBB-WORKSPACE.json 失败:' + e.message);
|
|
115
|
+
process.exit(2);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const targets = [{ name: 'workspace', dir: wsRoot }];
|
|
120
|
+
for (const name of appNames) {
|
|
121
|
+
const appDir = path.join(wsRoot, '.codespace', name);
|
|
122
|
+
if (!fs.existsSync(path.join(appDir, '.git'))) {
|
|
123
|
+
console.error('❌ 应用仓库不存在:.codespace/' + name + '(请检查 CBB-WORKSPACE.json 或先跑 cbb setup)');
|
|
124
|
+
process.exit(2);
|
|
125
|
+
}
|
|
126
|
+
targets.push({ name, dir: appDir });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ── 3. 逐仓库切换 ─────────────────────────────────────────
|
|
130
|
+
|
|
131
|
+
const results = [];
|
|
132
|
+
let hasFailure = false;
|
|
133
|
+
|
|
134
|
+
for (const t of targets) {
|
|
135
|
+
const cur = currentBranch(t.dir);
|
|
136
|
+
if (cur === branch) {
|
|
137
|
+
console.log('⏭️ ' + t.name + ': 已在 ' + branch + '(reused)');
|
|
138
|
+
results.push({ name: t.name, status: 'reused' });
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const exists = run('git rev-parse --verify --quiet refs/heads/' + branch, t.dir).ok;
|
|
143
|
+
const info = detectMain(t.dir);
|
|
144
|
+
let cmd;
|
|
145
|
+
let base = 'HEAD';
|
|
146
|
+
if (exists) {
|
|
147
|
+
cmd = 'git checkout "' + branch + '"';
|
|
148
|
+
base = '(existing)';
|
|
149
|
+
} else {
|
|
150
|
+
if (info.remote && info.main) base = info.remote + '/' + info.main;
|
|
151
|
+
else if (info.main) base = info.main;
|
|
152
|
+
cmd = 'git checkout -b "' + branch + '" "' + base + '"';
|
|
153
|
+
}
|
|
154
|
+
const r = run(cmd, t.dir);
|
|
155
|
+
if (r.ok) {
|
|
156
|
+
console.log('✅ ' + t.name + ': ' + (exists ? 'checkout' : 'created from ' + base) + ' → ' + branch);
|
|
157
|
+
results.push({ name: t.name, status: exists ? 'switched' : 'created', base });
|
|
158
|
+
} else {
|
|
159
|
+
console.error('❌ ' + t.name + ': 切换失败 — ' + r.err);
|
|
160
|
+
results.push({ name: t.name, status: 'failed', error: r.err });
|
|
161
|
+
hasFailure = true;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ── 4. 参与应用补装提交防线 hook(幂等)────────────────────
|
|
166
|
+
|
|
167
|
+
const hookResults = [];
|
|
168
|
+
for (const t of targets) {
|
|
169
|
+
if (t.name === 'workspace') continue; // 根仓库 hook 由 cbb setup 安装
|
|
170
|
+
hookResults.push({ name: t.name, status: installGitHook(t.dir) });
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
console.log('---');
|
|
174
|
+
console.log('LOCAL_START_RESULT', JSON.stringify({
|
|
175
|
+
branch,
|
|
176
|
+
results,
|
|
177
|
+
hooks: hookResults,
|
|
178
|
+
worktreeHint: '如需升级隔离:重跑 cbb-worktree-init(分支已存在自动复用,worktree 从分支创建,提交随身带走)',
|
|
179
|
+
}));
|
|
180
|
+
process.exit(hasFailure ? 2 : 0);
|
|
@@ -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)
|
|
@@ -12,12 +12,14 @@ description: "关闭/清理/删除需求工作空间的 worktree 和分支。触
|
|
|
12
12
|
## 核心原则
|
|
13
13
|
|
|
14
14
|
- 本技能的所有原子操作封装为 `scripts/*.js` Node.js 脚本,**跨平台执行**
|
|
15
|
-
- 本技能是 `cbb-worktree-init` 的逆操作:共用 `
|
|
16
|
-
- App worktree **物理嵌套**在 workspace worktree
|
|
15
|
+
- 本技能是 `cbb-worktree-init` 的逆操作:共用 `CBB-WORKSPACE.json`、共用 `.worktrees/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/<应用名>/` **是否实际存在**来确定关联应用——创建时用户可能只关联了 `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
|
|
|
@@ -34,7 +36,7 @@ node "<skill_dir>/scripts/<name>.js" <参数>
|
|
|
34
36
|
```
|
|
35
37
|
|
|
36
38
|
> - `<skill_dir>` = 本 SKILL.md 所在目录的绝对路径(AI 从读取路径即可推出)
|
|
37
|
-
> - **cwd 必须保持在工作空间根目录**:脚本按 cwd 定位 `
|
|
39
|
+
> - **cwd 必须保持在工作空间根目录**:脚本按 cwd 定位 `CBB-WORKSPACE.json` 与 git 仓库
|
|
38
40
|
> - 不要用 `./scripts/...` 裸相对路径(AI 的 cwd 不是技能目录,会解析失败)
|
|
39
41
|
|
|
40
42
|
---
|
|
@@ -62,9 +64,9 @@ node "<skill_dir>/scripts/find-workspace-root.js"
|
|
|
62
64
|
- 退出码 0 + 末行(纯路径) → WS_ROOT 绝对路径
|
|
63
65
|
- 退出码 1 → 未找到工作空间根目录,**终止流程**
|
|
64
66
|
|
|
65
|
-
> ⓘ **为什么不用
|
|
67
|
+
> ⓘ **为什么不用 CBB-WORKSPACE.json 存在性判断**:init-worktree 会把 CBB-WORKSPACE.json 提交到主分支,`git worktree add` 会自动 checkout 副本到 worktree 目录中。若用 CBB-WORKSPACE.json 存在性判断,在 worktree 内执行时会错误地把 worktree 目录当作 WS_ROOT。使用 `git worktree list` 的第一行(git 契约上始终为主仓库绝对路径)则不受该问题影响。
|
|
66
68
|
|
|
67
|
-
> close-worktree 是 init-worktree 的逆操作,但本步骤的 WS_ROOT 定位逻辑与 init-worktree 不同:init-worktree 强制根目录执行(不需要处理 worktree 场景),close-worktree 支持 worktree 内执行(需要绕开
|
|
69
|
+
> close-worktree 是 init-worktree 的逆操作,但本步骤的 WS_ROOT 定位逻辑与 init-worktree 不同:init-worktree 强制根目录执行(不需要处理 worktree 场景),close-worktree 支持 worktree 内执行(需要绕开 CBB-WORKSPACE.json 副本陷阱)。
|
|
68
70
|
|
|
69
71
|
---
|
|
70
72
|
|
|
@@ -97,26 +99,27 @@ node "<skill_dir>/scripts/find-target-worktree.js "<ws_root>"
|
|
|
97
99
|
node "<skill_dir>/scripts/discover-apps.js "<ws_root>" "<branch>"
|
|
98
100
|
```
|
|
99
101
|
|
|
100
|
-
脚本自动从 `
|
|
102
|
+
脚本自动从 `CBB-WORKSPACE.json` 读取应用列表,扫描 `.worktrees/worktree-<branch>/.codespace/<应用名>/`(兼容 ≤beta.23 旧平铺 `<应用名>/`)实际存在的子目录。
|
|
101
103
|
|
|
102
104
|
**解读末行 `DISCOVER_RESULT`**:
|
|
103
105
|
- `workspace` 字段:workspace worktree 绝对路径(AI agent 记录供后续命令使用)
|
|
104
106
|
- `apps` 数组:实际存在的应用 worktree 列表(每项含 `name`, `repo`, `side`, `wt_path`, `app_path`)
|
|
105
107
|
|
|
106
|
-
> 说明:app worktree 物理嵌套在 workspace worktree
|
|
108
|
+
> 说明:app worktree 物理嵌套在 workspace worktree 的 `.codespace/` 容器内,移除 workspace worktree 时嵌套的子目录会一并消失,但各 app 仓库的 git worktree 元数据(`.git/worktrees/`)仍需通过 `git worktree remove` 逐个清理。AI 配置经 junction 链入 worktree(只读内容子目录指向空间根唯一物理副本),关闭时由 remove-worktrees.js 在物理删除前**显式拆除**(只解除链接、源文件零接触),见 4.2 与附录 `junction-ai-config.js`。
|
|
107
109
|
|
|
108
110
|
### 1.3 检查未归档 change
|
|
109
111
|
|
|
110
112
|
```bash
|
|
111
|
-
node "<skill_dir>/scripts/check-unarchived.js "<ws_root>"
|
|
113
|
+
node "<skill_dir>/scripts/check-unarchived.js "<ws_root>" "<branch>"
|
|
112
114
|
```
|
|
113
115
|
|
|
114
|
-
|
|
116
|
+
脚本**双扫**(ADR 0002):空间根当前检出的 `openspec/changes/`(本地模式产物)与 `.worktrees/worktree-<branch>/openspec/changes/`(worktree 模式产物),返回未归档 change 名称合集。
|
|
115
117
|
|
|
116
118
|
**解读末行 `UNARCHIVED_RESULT`**:
|
|
117
|
-
- `changes`:未归档 change
|
|
118
|
-
-
|
|
119
|
-
- 退出码
|
|
119
|
+
- `changes`:未归档 change 名称合集(空数组 = 两处全部已归档)
|
|
120
|
+
- `detail`:每个 change 的来源(`root` / `worktree` / `both`)——Step 3.2 归档与 Step 4.1 执行归档时按来源定位目录
|
|
121
|
+
- 退出码 0 → 检查完成(无论是否有未归档 change;目录不存在视为该处无 change)
|
|
122
|
+
- 退出码 1 → 参数错误
|
|
120
123
|
|
|
121
124
|
> ⓘ 本检查为信息收集性质,不阻断流程。结果将在 Step 3 用户决策时展示。
|
|
122
125
|
|
|
@@ -211,70 +214,94 @@ AskUserQuestion({
|
|
|
211
214
|
|
|
212
215
|
**归档执行逻辑**(用户选择归档后,在 Step 4 中依次对每个待归档 change 执行):
|
|
213
216
|
|
|
217
|
+
> 落点按 Step 1.3 `detail` 的来源决定:`root` → 在 `<ws_root>/openspec/changes/` 执行;`worktree` → 在 `.worktrees/worktree-<branch>/openspec/changes/` 执行;`both` → 先向用户确认以哪份为准(通常 worktree 内为最新)。
|
|
218
|
+
|
|
214
219
|
1. `openspec status --change "<name>" --json` → 检查 artifacts 完成状态
|
|
215
220
|
2. 读取 `tasks.md` → 检查任务完成状态
|
|
216
221
|
3. 检查 `openspec/changes/<name>/specs/` → 判断 delta specs 是否需要同步
|
|
217
|
-
4. 执行归档(`mv openspec/changes/<name> openspec/changes/archive/YYYY-MM-DD-<name>`)
|
|
222
|
+
4. 执行归档(`mv <来源目录>/openspec/changes/<name> <来源目录>/openspec/changes/archive/YYYY-MM-DD-<name>`)
|
|
218
223
|
|
|
219
224
|
**逐项确认规则**:
|
|
220
225
|
- 若某个 change 有 artifacts 或 tasks 未完成 → 对该 change 单独弹出确认:*"<name> 有 N 个任务未完成,仍要归档吗?"*
|
|
221
226
|
- 若 delta specs 需要同步 → 对该 change 单独提示同步选项(同步 / 跳过同步直接归档)
|
|
222
227
|
- 每个 change 的归档结果独立记录(✅ 已归档 / ⏭️ 跳过 / ❌ 失败)
|
|
223
228
|
|
|
224
|
-
### 3.3
|
|
229
|
+
### 3.3 未推送 commit 处理(仅当 2.4 任一 worktree `unpushed_count > 0` 时出现)
|
|
225
230
|
|
|
226
|
-
|
|
231
|
+
**场景**:用户在 worktree 中做了 commit,但没有调 `cbb-worktree-push` 推送,本地有 ahead 提交。
|
|
227
232
|
|
|
228
|
-
|
|
233
|
+
**报告**:
|
|
234
|
+
```
|
|
235
|
+
⚠️ 检测到未推送的提交(ahead of origin/<branch>):
|
|
236
|
+
- workspace: 3 个 commit
|
|
237
|
+
- ep-user-info-server: 1 个 commit
|
|
229
238
|
|
|
230
|
-
|
|
231
|
-
-
|
|
239
|
+
请选择处理方式:
|
|
240
|
+
[先推送](推荐)调 cbb-worktree-push 推送所有 ahead 提交
|
|
241
|
+
[继续] 不单独推送,直接进入合并门禁(走「直合并」时会随合并一并推送到主干)
|
|
242
|
+
[中止] 终止流程,我先手动处理
|
|
243
|
+
```
|
|
232
244
|
|
|
233
|
-
|
|
245
|
+
- 用户选择「先推送」→ AI 引导执行 `cbb-worktree-push` skill(或 `/cbb-worktree-push`),完成后**重新跑** Step 2 safety-check 确认 `unpushed_count = 0` 再回到本步骤
|
|
246
|
+
- 用户选择「继续」→ 进入 Step 3.4;注意:走「提 MR」出口时会因 feature 未推送而失败,需回到本步骤先推送
|
|
247
|
+
- 用户选择「中止」→ 终止流程
|
|
234
248
|
|
|
235
|
-
|
|
249
|
+
> ⓘ 走「直合并」出口时,ahead 提交会被合并进主干并随 `git push origin <主干>` 一并推送,无需单独推送 feature 分支。
|
|
236
250
|
|
|
237
|
-
|
|
238
|
-
# 仅删本地分支(已合并自动 -d,未合并自动 -D;含 squash 合并 -D 兜底)
|
|
239
|
-
node "<skill_dir>/scripts/delete-branches.js" "<ws_root>" "<branch>" local
|
|
251
|
+
### 3.4 合并门禁决策(核心 · ADR 0007)
|
|
240
252
|
|
|
241
|
-
|
|
242
|
-
node "<skill_dir>/scripts/delete-branches.js" "<ws_root>" "<branch>" local_remote
|
|
243
|
-
```
|
|
253
|
+
依据 Step 2 的 `merged` 判定(每个参与仓库 `merged !== null` 即已合并,含 `main` / `origin/main` / `main-via-release-*`):
|
|
244
254
|
|
|
245
|
-
|
|
255
|
+
**情况 A:全部参与仓库已合并** → 无交互,直接进入 Step 4(合并成功后自动删分支)。
|
|
246
256
|
|
|
247
|
-
|
|
248
|
-
- **远程分支**:删除后无法直接恢复,需确认其他仓库/同事是否还在引用
|
|
249
|
-
- **推荐做法**:默认 `keep` 关闭 worktree 即可,确有必要再单独执行脚本;这样误删风险被显式手动动作兜住
|
|
257
|
+
**情况 B:存在未合并仓库** → 硬门禁触发,`AskUserQuestion` 三选一(**不允许"未合并且非废弃"地关闭**):
|
|
250
258
|
|
|
251
|
-
|
|
259
|
+
```
|
|
260
|
+
以下仓库的需求分支尚未交付(未合并到主干):
|
|
261
|
+
- workspace(merged: null)
|
|
262
|
+
- ep-foo-app(merged: null)
|
|
263
|
+
|
|
264
|
+
请选择交付方式:
|
|
265
|
+
[提 MR](推荐) 生成预填合并请求链接,由人工在浏览器合入
|
|
266
|
+
[直合并] 由 agent 直接合并到主干并推送远端(需你确认)
|
|
267
|
+
[确认废弃] 该分支不再交付,远程重命名为 <分支>-deprecated
|
|
268
|
+
```
|
|
252
269
|
|
|
253
|
-
|
|
270
|
+
**出口一 · 提 MR(推荐)**
|
|
254
271
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
- 一旦 close worktree + 后续走 `local` / `local_remote` 删分支,ahead 提交可能永久丢失(取决于上游 `delete-branches.js` 行为)
|
|
272
|
+
1. 先确保 feature 已推送(Step 3.3 未推送时先推送)
|
|
273
|
+
2. **引导用户运行 `cbb-worktree-push`**(说"提交并推送"):其 Step 7 会列出目标分支候选(`git branch -r` 现场候选,**禁止 AI 代决**)并生成 MR 预填链接(可选自动开浏览器);本技能不自带 MR 链接脚本
|
|
274
|
+
3. **中止 close**:提示"合并完成后重跑本技能"。**不执行任何移除 / 删除**
|
|
259
275
|
|
|
260
|
-
|
|
276
|
+
**出口二 · 直合并(`merge-branches.js`)**
|
|
277
|
+
|
|
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>"
|
|
261
283
|
```
|
|
262
|
-
⚠️ 检测到未推送的提交(ahead of origin/<branch>):
|
|
263
|
-
- workspace: 3 个 commit
|
|
264
|
-
- ep-user-info-server: 1 个 commit
|
|
265
|
-
- ep-baz-app: 2 个 commit
|
|
266
284
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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>"
|
|
271
299
|
```
|
|
272
300
|
|
|
273
|
-
-
|
|
274
|
-
-
|
|
275
|
-
- 用户选择「中止」→ 终止流程
|
|
301
|
+
- 效果:远程存在该分支的仓库,重命名 `<分支>` → `<分支>-deprecated`(**保留历史、不删提交**);**本地分支保持原名不动**
|
|
302
|
+
- 解读末行 `DEPRECATE_RESULT`:`failed` → 报告并终止
|
|
276
303
|
|
|
277
|
-
> ⓘ
|
|
304
|
+
> ⓘ 门禁判定复用 `safety-check` 的 `merged`:他人经 MR 或 `release-*` 中转合入后,重跑本技能即自动放行,无需重复交付。
|
|
278
305
|
|
|
279
306
|
---
|
|
280
307
|
|
|
@@ -302,11 +329,12 @@ node "<skill_dir>/scripts/delete-branches.js" "<ws_root>" "<branch>" local_remot
|
|
|
302
329
|
node "<skill_dir>/scripts/remove-worktrees.js "<ws_root>" "<branch>" "<force>"
|
|
303
330
|
```
|
|
304
331
|
|
|
305
|
-
|
|
332
|
+
**移除流程**:`git worktree remove` 实际包含两个动作——①清理 git 元数据(`.git/worktrees/`)②删除物理目录。当 IDE 占用目录时 ② 会被 Windows 文件锁阻塞,但 ① 永远不受影响。脚本将这两个动作解耦,并在物理删除前**先显式拆除 AI 配置 junction**(`junction-ai-config.js --remove`:只解除链接、目标在空间根内校验、源文件零接触):
|
|
306
333
|
|
|
307
|
-
- **第 1 层(正常路径):** `git worktree remove` 一步完成元数据 +
|
|
308
|
-
- **第
|
|
309
|
-
- **第
|
|
334
|
+
- **第 1 层(正常路径):** junction 拆除 → `git worktree remove` 一步完成元数据 + 物理目录清理,并**验证物理目录已真消失**
|
|
335
|
+
- **第 1.5 层(git 静默失败兜底):** 实测 Windows 上 `git worktree remove` 遇 junction 时可能**返回成功但物理目录残留**(本次修复前 junction 遗留的根因)——检测目录仍存在即自动走第 2 层
|
|
336
|
+
- **第 2 层(解耦清理):** 先 `prune` 清理 git 元数据(始终成功),再 `fs.rmSync`(Node 原生,对 reparse point 只删链接不穿透)尝试物理删除
|
|
337
|
+
- **第 3 层(延迟清理):** Windows 下 IDE 持有排他句柄时触发。元数据已清理保证分支删除不受影响;物理目录**不会被 init 自动删除**(0.0.3-beta.11 起 init 只 prune 元数据、绝不删目录,防误删活跃 worktree),需用户关闭 IDE 后手动删除,或重跑 close 补删
|
|
310
338
|
|
|
311
339
|
**解读末行 `REMOVE_RESULT`**:
|
|
312
340
|
- `has_locked: false` → 全部清理成功
|
|
@@ -320,48 +348,52 @@ node "<skill_dir>/scripts/remove-worktrees.js "<ws_root>" "<branch>" "<force>"
|
|
|
320
348
|
|
|
321
349
|
所有 worktree 已成功移除后,如果当前 shell 的工作目录位于**已被关闭的 worktree 内**,需要主动让 IDE 窗口切回 `WS_ROOT`,避免 IDE 窗口指向已删除的目录。
|
|
322
350
|
|
|
323
|
-
> ⓘ **本节是 close-worktree 专属逻辑**:与 init-worktree 的 Step 5 不共享、不交叉同步。init-worktree 负责"
|
|
351
|
+
> ⓘ **本节是 close-worktree 专属逻辑**:与 init-worktree 的 Step 5 不共享、不交叉同步。init-worktree 负责"创建后把开发会话开进 worktree",本节负责"关闭后切回根目录",两者职责独立。
|
|
324
352
|
|
|
325
353
|
**AI agent 行为**:
|
|
326
|
-
1.
|
|
327
|
-
2.
|
|
354
|
+
1. 检查当前会话 / IDE 窗口是否指向被关闭的 worktree
|
|
355
|
+
2. **OpenCode 等支持会话搬移的工具**:agent 亲自经 code-mode 调用 `opencode.session_move` 把会话切回 `<ws_root>`——自动完成、上下文保留、**不列入用户待办**,切完核对工作目录
|
|
356
|
+
3. **Qoder / Cursor / VS Code**:调用对应 IDE 的 `--reuse-window` 命令切回 WS_ROOT:
|
|
328
357
|
- Qoder → `qoder --reuse-window "<ws_root>"`
|
|
329
358
|
- Cursor → `cursor --reuse-window "<ws_root>"`
|
|
330
359
|
- VS Code → `code --reuse-window "<ws_root>"`
|
|
331
|
-
|
|
360
|
+
4. **JetBrains 跳过主动切换**——`idea64 <path>` 会启动新进程或提示"打开/在新窗口打开",让用户手动操作
|
|
332
361
|
|
|
333
362
|
**关键点**:
|
|
334
|
-
-
|
|
363
|
+
- **只在会话 / 窗口匹配被关闭的 worktree 时触发**——如果已在 WS_ROOT 内,再切一次是多余的
|
|
364
|
+
- **OpenCode 会话切回是零成本自动动作**——`session_move` 复用当前会话(上下文保留、无新窗口),agent 亲自做,严禁推给用户
|
|
335
365
|
- **仅切 Qoder / Cursor / VS Code**——这三者的 `--reuse-window` 是轻量级"切目录",不重启 IDE
|
|
336
366
|
- **不会产生孤立窗口**——`--reuse-window` 是复用当前窗口切换目录,**不会打开新窗口**。即使 IDE 已开着指向 WS_ROOT 的其他窗口,本步骤也不会重复创建,仅是把当前指向已删除目录的窗口切走
|
|
337
|
-
-
|
|
367
|
+
- **失败不致命**——`session_move` / `--reuse-window` 失败时仅警告,后续分支删除不受影响
|
|
338
368
|
|
|
339
|
-
### 4.4
|
|
369
|
+
### 4.4 分支处理(合并成功后自动删除 · ADR 0007)
|
|
340
370
|
|
|
341
|
-
|
|
371
|
+
**按 Step 3.4 的门禁出口决定**:
|
|
342
372
|
|
|
343
|
-
|
|
373
|
+
- **交付完成**(情况 A 已合并,或出口二直合并成功)→ **自动删除**各参与仓库的本地 + 远程 feature 分支:
|
|
344
374
|
|
|
345
375
|
```bash
|
|
346
|
-
node "<skill_dir>/scripts/delete-branches.js" "<ws_root>" "<branch>" local
|
|
347
|
-
# 或
|
|
348
376
|
node "<skill_dir>/scripts/delete-branches.js" "<ws_root>" "<branch>" local_remote
|
|
349
377
|
```
|
|
350
378
|
|
|
379
|
+
- **出口三废弃** → **保留分支**(远程已重命名为 `<分支>-deprecated`),**不调用** `delete-branches.js`
|
|
380
|
+
|
|
381
|
+
**删除前必做**:输出"即将删除的本地 / 远程分支清单"(逐仓库),透明可见。
|
|
382
|
+
|
|
351
383
|
脚本内部对每个仓库:
|
|
352
384
|
- **本地分支**:检测是否已合并(复用 safety-check 的合并判定算法)
|
|
353
385
|
- 已合并 → `git branch -d`(安全删除)
|
|
354
386
|
- 未合并 → `git branch -D`(强制删除)
|
|
355
387
|
- squash 合并可能让 `-d` 失败 → 自动回退到 `-D`(squash 合并同样安全)
|
|
356
|
-
-
|
|
388
|
+
- **远程分支**(`local_remote` 模式):`git push <remote> --delete <branch>`
|
|
357
389
|
- 分支不存在时 push --delete 会报错,按"已删除/本就无"处理即可
|
|
358
390
|
- 远程引用已在 Step 2.0 主动 fetch 刷新并持久化在 .git,此处直接读取无需重复 fetch
|
|
359
391
|
|
|
360
|
-
**解读末行 `BRANCH_DELETE_RESULT
|
|
361
|
-
|
|
362
|
-
|
|
392
|
+
**解读末行 `BRANCH_DELETE_RESULT`**:`mode`(keep / local / local_remote)、`ok`。
|
|
393
|
+
|
|
394
|
+
**失败处理(ADR 0007)**:清理阶段(删除分支)失败**报告并继续其余仓库,不回滚**——交付已达成,清理不净不该阻塞(与合并阶段的"失败即停"不同)。
|
|
363
395
|
|
|
364
|
-
|
|
396
|
+
**顺序硬约束**:必须在 Step 4.2(移除 worktree)**之后**执行——分支正检出于 worktree,先删会报 `Cannot delete branch ... checked out at ...`。
|
|
365
397
|
|
|
366
398
|
> 关于 `.gitignore`:创建时把 `.worktrees/` 加入 `.gitignore` 并**提交到了 feature 分支**(非主分支)。
|
|
367
399
|
> - 分支未合并、被 `-D` 删除 → 这些 commit 随分支一起消失,主分支的 `.gitignore` 不受影响
|
|
@@ -381,6 +413,10 @@ git -C "<ws_root>" worktree list
|
|
|
381
413
|
|
|
382
414
|
目标 worktree 应已不在列表中。
|
|
383
415
|
|
|
416
|
+
**产物留存检查(打包产物 · 不自动删除)**:检查 `<ws_root>/.build/<branch>/` 是否存在(`test -d`),并统计 `.build/` 全景(需求目录数与总体积,Node 或 `du -sh` 等价实现)。**关闭流程绝不自动删除 `.build/` 内容**(打包产物是交付后的取件来源,删错不可逆);用户明确表示不需要时,才按其指定范围清理。
|
|
417
|
+
|
|
418
|
+
**工作区回根交接(必须执行)**:若用户的开发会话工作在 worktree 内(当前会话工作目录 = 已删除的 `<wt_path>`),此刻目录已悬空——**OpenCode 等支持会话搬移的工具由 agent 自动 `session_move` 切回 `<ws_root>`,不列入用户待办**;无该能力的工具才提示用户把工作区切回 `<ws_root>`(打开文件夹 / Open Folder / 切换工作目录),并在最终报告中给出该绝对路径。
|
|
419
|
+
|
|
384
420
|
---
|
|
385
421
|
|
|
386
422
|
## 最终报告
|
|
@@ -388,17 +424,24 @@ git -C "<ws_root>" worktree list
|
|
|
388
424
|
```
|
|
389
425
|
✅ 工作空间 worktree 已关闭
|
|
390
426
|
当前目录:<工作空间根目录绝对路径>
|
|
427
|
+
交付方式:已合并(MR 合入)/ 直合并(agent 合并并推送主干)/ 确认废弃
|
|
391
428
|
已移除 worktree:
|
|
392
429
|
- .worktrees/worktree-feature-xxx(workspace)
|
|
393
|
-
- .worktrees/worktree-feature-xxx/ep-user-info-server(嵌套,ep-user-info-server 仓库)
|
|
394
|
-
- .worktrees/worktree-feature-xxx/ep-user-management-app(嵌套,ep-user-management-app 仓库)
|
|
430
|
+
- .worktrees/worktree-feature-xxx/.codespace/ep-user-info-server(嵌套,ep-user-info-server 仓库)
|
|
431
|
+
- .worktrees/worktree-feature-xxx/.codespace/ep-user-management-app(嵌套,ep-user-management-app 仓库)
|
|
395
432
|
未提交修改:已丢弃(--force)/ 无
|
|
396
433
|
未归档 change 处理:
|
|
397
434
|
✅ ai-call-send-im-msg → 已归档到 archive/2026-07-08-ai-call-send-im-msg
|
|
398
435
|
⏭️ fix-payment-timeout → 用户选择跳过
|
|
399
436
|
(无未归档 change 时不显示本节)
|
|
400
|
-
分支处理:
|
|
437
|
+
分支处理:
|
|
438
|
+
- 交付完成 → 已删除本地 + 远程分支:feature-xxx(逐仓库列出)
|
|
439
|
+
- 已废弃 → 远程已重命名为 feature-xxx-deprecated(本地分支保留原名)
|
|
401
440
|
剩余工作空间 worktree:<列出仍存在的,或 "无" >
|
|
441
|
+
产物留存:.build/<branch>/(已保留;不需要可告诉我清理)
|
|
442
|
+
.build 全景:共 N 个需求目录 / ~X MB(可顺带清理已交付或已废弃需求)
|
|
443
|
+
(产物留支行仅在该目录存在时显示)
|
|
444
|
+
👉 仅当所用工具不支持自动切根、且你的窗口仍停在已删除的 worktree 时,请手动切回工作空间根:<ws_root 绝对路径>
|
|
402
445
|
Workspace cleaned up.
|
|
403
446
|
```
|
|
404
447
|
|
|
@@ -417,18 +460,24 @@ git -C "<ws_root>" worktree list
|
|
|
417
460
|
### 绝不允许
|
|
418
461
|
|
|
419
462
|
- 先删分支再删 worktree(分支正检出于 worktree,会报 `Cannot delete branch ... checked out at ...`)
|
|
420
|
-
-
|
|
463
|
+
- **未合并且未确认为废弃时关闭 worktree**(违反 ADR 0007 硬门禁)
|
|
464
|
+
- 未经用户确认用 `--force` 丢弃未提交修改;或对未确认废弃的未合并分支执行 `-D` 强删
|
|
465
|
+
- 未经二次确认执行 `deprecate-branches.js`(远程重命名不可逆)
|
|
466
|
+
- 直合并前跳过"已归档 + 已提交"前置校验
|
|
421
467
|
- 操作 `.worktrees/worktree-<需求名>` 以外的任何 worktree(如 `~/.qoder/worktree/...` 会话 worktree)
|
|
422
468
|
- 移除/删除前跳过 Step 2 安全评估
|
|
423
469
|
- 删除工作空间根目录的 AI 配置目录(`.claude/`、`.qoder/`、`.opencode/` 等)
|
|
470
|
+
- 未经用户确认删除 `.build/` 内的打包产物(留存目录的清理须用户逐项确认;关闭流程绝不自动删)
|
|
424
471
|
- 用 `AskUserQuestion` 收集自由文本(分支名等直接对话提问)
|
|
425
472
|
- 手动改任何仓库的 `.gitignore`(关闭流程不碰它)
|
|
426
473
|
- 运行环境 node 不可用时强行继续 → 改为 fail-fast 提示用户
|
|
427
474
|
|
|
428
475
|
### 必须执行
|
|
429
476
|
|
|
430
|
-
- 读 `
|
|
477
|
+
- 读 `CBB-WORKSPACE.json` 取应用名列表;以 `.worktrees/worktree-<需求名>/.codespace/<应用名>/` 实际存在性确定关联清单
|
|
431
478
|
- 移除/删除前执行安全评估(未提交修改 / 是否合并 / 是否推远程)并报告
|
|
479
|
+
- **按 Step 3.4 门禁三选一交付**;未合并且未确认为废弃时**禁止关闭**
|
|
480
|
+
- 交付完成(自动删分支)前**先输出将删清单**;清理阶段失败报告并继续,不回滚
|
|
432
481
|
- 严格按 Step 4.2(移除 worktree)→ Step 4.4(删分支)顺序
|
|
433
482
|
- 移除 workspace worktree 前先把会话移出(`cd "<WS_ROOT>" &&` 或 `git -C` 绝对路径)
|
|
434
483
|
- 收尾切回 WS 根目录、`pwd` 确认
|
|
@@ -438,7 +487,7 @@ git -C "<ws_root>" worktree list
|
|
|
438
487
|
- **合并判定依赖刷新后的远程引用,故 Step 2.0 先主动 `git fetch`**:feature 多由他人经 MR 合入远端,本地引用否则会陈旧误判
|
|
439
488
|
- **并行块用临时文件收结果、不用共享变量**:原 bash 版本中 `&` 后台任务跑在独立子 shell;Node.js 同步执行无此问题
|
|
440
489
|
- **`.gitignore` 无需清理**:创建时的 `.worktrees/` 条目提交在 feature 分支上,删分支时随分支消失(未合并)或无害保留(已合并)
|
|
441
|
-
- **AI
|
|
490
|
+
- **AI 配置在 worktree 内是 junction(链接)**:物理副本只在空间根(单层安装)。**不可依赖「随目录树消失」**——Windows 上 `git worktree remove` 遇 junction 会静默失败、残留整个目录树(实测复现)。关闭流程在物理删除前用 Node 显式拆除(只解除链接,目标在空间根内校验,源文件零接触)
|
|
442
491
|
- **部分 `worktree remove` 失败**:已移除的无需回退,未移除的报告详情让用户处理后重跑
|
|
443
492
|
- **Windows IDE 目录锁的三层降级**:`git worktree remove` 内部做两件事——①清元数据 ②删目录。Windows 文件锁只阻塞 ②,不阻塞 ①
|
|
444
493
|
|
|
@@ -452,7 +501,10 @@ git -C "<ws_root>" worktree list
|
|
|
452
501
|
| `find-workspace-root.js` | 定位工作空间根目录(共享脚本,源仓库真源在 `_shared/scripts/`) | 无 | 0/1 |
|
|
453
502
|
| `find-target-worktree.js` | 目标 worktree 识别(共享脚本,源仓库真源在 `_shared/scripts/`) | ws_root | 0/1/2 |
|
|
454
503
|
| `discover-apps.js` | 发现关联应用 worktree | ws_root, branch | 0/1/2 |
|
|
455
|
-
| `check-unarchived.js` | 检查未归档 change | ws_root | 0/1 |
|
|
504
|
+
| `check-unarchived.js` | 检查未归档 change(双扫空间根 + worktree) | ws_root, branch | 0/1 |
|
|
456
505
|
| `safety-check.js` | 安全评估(合并判定 + 未提交 + 远程) | ws_root, branch | 0/1 |
|
|
457
|
-
| `remove-worktrees.js` | 移除 worktree
|
|
458
|
-
| `
|
|
506
|
+
| `remove-worktrees.js` | 移除 worktree(先拆 junction,三层降级 + 残留验证) | ws_root, branch, force | 0/1 |
|
|
507
|
+
| `junction-ai-config.js` | AI 配置 junction 创建 / `--remove` 拆除(共享脚本,源仓库真源在 `_shared/scripts/`) | ws_root, wt_path, [--remove] | 0/1/2 |
|
|
508
|
+
| `delete-branches.js` | 删除分支(按 mode;交付完成后由 Step 4.4 自动以 local_remote 调用) | ws_root, branch, mode | 0/1/2 |
|
|
509
|
+
| `merge-branches.js` | 收尾合并门禁·直合并(预检 → merge --no-ff → push 主干,共享脚本,真源在 `_shared/scripts/`) | ws_root, branch, --apps / --target / --allow-checkout / --dry-run / --check | 0/1/2 |
|
|
510
|
+
| `deprecate-branches.js` | 收尾合并门禁·废弃(远程分支重命名为 `<分支>-deprecated`,共享脚本) | ws_root, branch, --apps | 0/1/2 |
|
|
@@ -3,14 +3,14 @@ require('./silence-popup');
|
|
|
3
3
|
/**
|
|
4
4
|
* cbb-worktree-close 环境健康探活
|
|
5
5
|
*
|
|
6
|
-
* close-worktree 必须在工作空间根目录或 worktree 内执行,且需找到
|
|
7
|
-
* 与 init
|
|
6
|
+
* close-worktree 必须在工作空间根目录或 worktree 内执行,且需找到 CBB-WORKSPACE.json。
|
|
7
|
+
* 与 init 探活同构,但额外校验 CBB-WORKSPACE.json 存在性。
|
|
8
8
|
*
|
|
9
9
|
* 退出码:
|
|
10
10
|
* 0 - 探活通过
|
|
11
11
|
* 1 - 致命错误(git 不可用)
|
|
12
12
|
* 2 - 当前不在 git 仓库
|
|
13
|
-
* 3 - 未找到
|
|
13
|
+
* 3 - 未找到 CBB-WORKSPACE.json
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
const { execSync } = require('child_process');
|