@jspg-ai/coding-bb 0.0.3-beta.9 → 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 +131 -120
- 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 +17 -5
- 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
|
@@ -1,48 +1,77 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
require('./silence-popup');
|
|
3
|
-
/**
|
|
4
|
-
*
|
|
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
|
-
|
|
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);
|
|
@@ -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);
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
require('./silence-popup');
|
|
3
|
+
/**
|
|
4
|
+
* 收尾合并门禁 —— 废弃分支出口(ADR 0007)
|
|
5
|
+
*
|
|
6
|
+
* 用户在门禁处确认"分支废弃、不再交付"后,把各参与仓库远端的需求分支重命名为
|
|
7
|
+
* <branch>-deprecated(保留历史、去掉占用中的旧名),**不删除任何提交**。
|
|
8
|
+
* 本地分支保持原名不动;未推送的仓库(远端无该分支)跳过。
|
|
9
|
+
*
|
|
10
|
+
* 用法:
|
|
11
|
+
* node deprecate-branches.js <ws_root> <branch> [--apps a,b]
|
|
12
|
+
*
|
|
13
|
+
* 退出码:0 全部成功(含 skipped)/ 1 参数错误 / 2 存在失败
|
|
14
|
+
* 末行:DEPRECATE_RESULT [...]
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const { execSync } = require('child_process');
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
|
|
21
|
+
function run(cmd, cwd) {
|
|
22
|
+
try {
|
|
23
|
+
return {
|
|
24
|
+
ok: true,
|
|
25
|
+
out: execSync(cmd, {
|
|
26
|
+
cwd,
|
|
27
|
+
encoding: 'utf-8',
|
|
28
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
29
|
+
timeout: 120000,
|
|
30
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
|
|
31
|
+
}).trim(),
|
|
32
|
+
};
|
|
33
|
+
} catch (e) {
|
|
34
|
+
return { ok: false, err: (e.stderr || e.message || String(e)).trim() };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function loadApps(wsRoot) {
|
|
39
|
+
try {
|
|
40
|
+
const config = JSON.parse(fs.readFileSync(path.join(wsRoot, 'CBB-WORKSPACE.json'), 'utf-8'));
|
|
41
|
+
return Array.isArray(config.apps) ? config.apps : [];
|
|
42
|
+
} catch (e) {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** 对单个仓库执行废弃重命名 */
|
|
48
|
+
function deprecateRepo(name, repo, branch) {
|
|
49
|
+
const newName = branch + '-deprecated';
|
|
50
|
+
const rec = { name, status: 'skipped', detail: null };
|
|
51
|
+
if (!fs.existsSync(path.join(repo, '.git'))) {
|
|
52
|
+
rec.detail = 'no-repo';
|
|
53
|
+
return rec;
|
|
54
|
+
}
|
|
55
|
+
const remote = run('git remote', repo);
|
|
56
|
+
if (!remote.ok || !remote.out) {
|
|
57
|
+
rec.detail = 'no-remote';
|
|
58
|
+
return rec;
|
|
59
|
+
}
|
|
60
|
+
const r = remote.out.split(/\r?\n/)[0];
|
|
61
|
+
// 仅重命名远端确实存在的分支
|
|
62
|
+
const exists = run('git ls-remote --heads "' + r + '" "refs/heads/' + branch + '"', repo);
|
|
63
|
+
if (!exists.ok || !exists.out) {
|
|
64
|
+
rec.detail = 'remote-has-no-branch';
|
|
65
|
+
return rec;
|
|
66
|
+
}
|
|
67
|
+
const pushNew = run('git push "' + r + '" "refs/heads/' + branch + ':refs/heads/' + newName + '"', repo);
|
|
68
|
+
if (!pushNew.ok) {
|
|
69
|
+
rec.status = 'failed';
|
|
70
|
+
rec.detail = 'push-new-failed: ' + (pushNew.err || '').split(/\r?\n/)[0];
|
|
71
|
+
return rec;
|
|
72
|
+
}
|
|
73
|
+
const delOld = run('git push "' + r + '" --delete "' + branch + '"', repo);
|
|
74
|
+
if (!delOld.ok) {
|
|
75
|
+
rec.status = 'failed';
|
|
76
|
+
rec.detail = 'delete-old-failed: ' + (delOld.err || '').split(/\r?\n/)[0];
|
|
77
|
+
return rec;
|
|
78
|
+
}
|
|
79
|
+
rec.status = 'deprecated';
|
|
80
|
+
rec.detail = 'remote ' + branch + ' → ' + newName;
|
|
81
|
+
return rec;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function main() {
|
|
85
|
+
const args = process.argv.slice(2);
|
|
86
|
+
const wsRoot = args[0];
|
|
87
|
+
const branch = args[1];
|
|
88
|
+
|
|
89
|
+
if (!wsRoot || !branch || branch.indexOf('--') === 0) {
|
|
90
|
+
console.error('Usage: node deprecate-branches.js <ws_root> <branch> [--apps a,b]');
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const appsIdx = args.indexOf('--apps');
|
|
95
|
+
const appFilter = appsIdx >= 0
|
|
96
|
+
? new Set((args[appsIdx + 1] || '').split(',').map(s => s.trim()).filter(Boolean))
|
|
97
|
+
: null;
|
|
98
|
+
|
|
99
|
+
const repos = [{ name: 'workspace', repo: wsRoot }];
|
|
100
|
+
for (const app of loadApps(wsRoot)) {
|
|
101
|
+
if (appFilter && !appFilter.has(app.name)) continue;
|
|
102
|
+
repos.push({ name: app.name, repo: path.join(wsRoot, '.codespace', app.name) });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const results = repos.map(rp => deprecateRepo(rp.name, rp.repo, branch));
|
|
106
|
+
|
|
107
|
+
results.forEach(r => {
|
|
108
|
+
console.log(r.status.toUpperCase() + ' ' + r.name + (r.detail ? ' — ' + r.detail : ''));
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const ok = results.every(r => r.status !== 'failed');
|
|
112
|
+
console.log('---');
|
|
113
|
+
console.log('DEPRECATE_RESULT', JSON.stringify({ branch, new_name: branch + '-deprecated', results }));
|
|
114
|
+
process.exit(ok ? 0 : 2);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (require.main === module) main();
|
|
118
|
+
|
|
119
|
+
module.exports = { run, loadApps, deprecateRepo };
|
|
@@ -5,7 +5,7 @@ require('./silence-popup');
|
|
|
5
5
|
*
|
|
6
6
|
* 用法:node discover-apps.js <ws_root> <branch>
|
|
7
7
|
*
|
|
8
|
-
* 从
|
|
8
|
+
* 从 CBB-WORKSPACE.json 读取应用列表,扫描 .worktrees/worktree-<branch>/ 实际存在的子目录。
|
|
9
9
|
*
|
|
10
10
|
* 输出末行:DISCOVER_RESULT { workspace: <wt_path>, apps: [{name, repo, side, wt_path}] }
|
|
11
11
|
*
|
|
@@ -26,9 +26,9 @@ if (!wsRoot || !branch) {
|
|
|
26
26
|
process.exit(1);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
const configPath = path.join(wsRoot, '
|
|
29
|
+
const configPath = path.join(wsRoot, 'CBB-WORKSPACE.json');
|
|
30
30
|
if (!fs.existsSync(configPath)) {
|
|
31
|
-
console.error('❌
|
|
31
|
+
console.error('❌ CBB-WORKSPACE.json 不存在:' + configPath);
|
|
32
32
|
process.exit(1);
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -36,7 +36,7 @@ let config;
|
|
|
36
36
|
try {
|
|
37
37
|
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
38
38
|
} catch (e) {
|
|
39
|
-
console.error('❌
|
|
39
|
+
console.error('❌ CBB-WORKSPACE.json 解析失败:' + e.message);
|
|
40
40
|
process.exit(1);
|
|
41
41
|
}
|
|
42
42
|
|
|
@@ -50,9 +50,18 @@ if (!fs.existsSync(wtDir)) {
|
|
|
50
50
|
process.exit(2);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/** 解析 app worktree 物理路径:新布局 .codespace/<app> 优先,兼容 ≤beta.23 旧平铺 <app> */
|
|
54
|
+
function resolveAppWt(appName) {
|
|
55
|
+
const nested = path.join(wtDir, '.codespace', appName);
|
|
56
|
+
if (fs.existsSync(path.join(nested, '.git'))) return nested;
|
|
57
|
+
const legacy = path.join(wtDir, appName);
|
|
58
|
+
if (fs.existsSync(path.join(legacy, '.git'))) return legacy;
|
|
59
|
+
return nested;
|
|
60
|
+
}
|
|
61
|
+
|
|
53
62
|
const found = [];
|
|
54
63
|
for (const app of apps) {
|
|
55
|
-
const appWt =
|
|
64
|
+
const appWt = resolveAppWt(app.name);
|
|
56
65
|
if (fs.existsSync(appWt) && fs.existsSync(path.join(appWt, '.git'))) {
|
|
57
66
|
console.log('✅ ' + app.name + ':存在嵌套 worktree(' + appWt + ')');
|
|
58
67
|
found.push({
|