@jspg-ai/coding-bb 0.0.3-beta.1 → 0.0.3-beta.12
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 +118 -116
- package/cbb/lib/install/claude-code.js +1 -3
- package/cbb/lib/install/codebuddy.js +30 -0
- package/cbb/lib/install/init.js +295 -119
- package/cbb/lib/install/opencode.js +35 -31
- package/cbb/lib/install/qoder.js +1 -3
- package/cbb/lib/install/rule-rewrite.js +27 -0
- package/cbb/lib/install/trae.js +33 -0
- package/cbb/lib/openspec/index.js +337 -554
- package/cbb/lib/superpowers/index.js +246 -265
- package/cbb/lib/utils/check-update.js +8 -1
- package/cbb/lib/utils/gitignore.js +2 -0
- package/cbb/lib/utils/settings.js +17 -4
- package/cbb/lib/utils/tar.js +92 -0
- package/cbb/lib/utils/upstream.js +90 -0
- package/cbb/worktrees/skills/cbb-worktree-close/SKILL.md +32 -23
- package/cbb/worktrees/skills/cbb-worktree-close/scripts/remove-worktrees.js +2 -2
- package/cbb/worktrees/skills/cbb-worktree-init/SKILL.md +36 -28
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/create-worktrees.js +15 -37
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/update-gitignore.js +25 -9
- package/cbb/worktrees/skills/cbb-worktree-push/SKILL.md +66 -30
- package/config/openspec/schemas/spec-driven/schema.yaml +29 -30
- package/config/openspec/schemas/spec-driven/templates/design.md +0 -18
- package/config/openspec/schemas/spec-driven/templates/proposal.md +4 -3
- package/config/upstream-mirrors.json +12 -0
- package/config/workspace-agents.sample.md +41 -39
- package/openspec/.version +2 -3
- package/openspec/commands/apply.md +189 -175
- package/openspec/commands/archive.md +237 -216
- package/openspec/commands/bulk-archive.md +355 -327
- package/openspec/commands/continue.md +116 -105
- package/openspec/commands/explore.md +230 -199
- package/openspec/commands/ff.md +115 -104
- package/openspec/commands/new.md +74 -63
- package/openspec/commands/onboard.md +557 -548
- package/openspec/commands/propose.md +161 -150
- package/openspec/commands/sync.md +277 -249
- package/openspec/commands/update.md +92 -80
- package/openspec/commands/verify.md +175 -162
- package/openspec/skills/openspec-apply-change/SKILL.md +20 -5
- package/openspec/skills/openspec-archive-change/SKILL.md +30 -8
- package/openspec/skills/openspec-bulk-archive-change/SKILL.md +36 -6
- package/openspec/skills/openspec-continue-change/SKILL.md +14 -2
- package/openspec/skills/openspec-explore/SKILL.md +21 -9
- package/openspec/skills/openspec-ff-change/SKILL.md +14 -2
- package/openspec/skills/openspec-new-change/SKILL.md +13 -1
- package/openspec/skills/openspec-onboard/SKILL.md +49 -39
- package/openspec/skills/openspec-propose/SKILL.md +15 -3
- package/openspec/skills/openspec-sync-specs/SKILL.md +31 -2
- package/openspec/skills/openspec-update-change/SKILL.md +27 -14
- package/openspec/skills/openspec-verify-change/SKILL.md +17 -3
- package/package.json +2 -2
- package/superpowers/.version +4 -4
- package/superpowers/skills/brainstorming/SKILL.md +47 -12
- package/superpowers/skills/brainstorming/scripts/frame-template.html +213 -213
- package/superpowers/skills/brainstorming/scripts/server.cjs +723 -723
- package/superpowers/skills/brainstorming/visual-companion.md +6 -6
- package/superpowers/skills/diagnosing-superpowers/SKILL.md +120 -0
- package/superpowers/skills/diagnosing-superpowers/prompts/analyst-common.md +38 -0
- package/superpowers/skills/diagnosing-superpowers/prompts/cost-and-time.md +28 -0
- package/superpowers/skills/diagnosing-superpowers/prompts/plan-adherence.md +29 -0
- package/superpowers/skills/diagnosing-superpowers/prompts/quality-evidence.md +26 -0
- package/superpowers/skills/diagnosing-superpowers/prompts/repeated-work.md +30 -0
- package/superpowers/skills/diagnosing-superpowers/prompts/request-conflicts.md +20 -0
- package/superpowers/skills/diagnosing-superpowers/prompts/scrub-audit.md +33 -0
- package/superpowers/skills/diagnosing-superpowers/prompts/scrub.md +29 -0
- package/superpowers/skills/diagnosing-superpowers/prompts/similar-session.md +38 -0
- package/superpowers/skills/diagnosing-superpowers/prompts/skill-timeline.md +30 -0
- package/superpowers/skills/diagnosing-superpowers/prompts/stumbles.md +28 -0
- package/superpowers/skills/diagnosing-superpowers/references/context-safety.md +22 -0
- package/superpowers/skills/diagnosing-superpowers/references/github-issues.md +47 -0
- package/superpowers/skills/diagnosing-superpowers/references/redaction-policy.md +34 -0
- package/superpowers/skills/diagnosing-superpowers/references/session-discovery.md +31 -0
- package/superpowers/skills/diagnosing-superpowers/templates/bundle-README.md +77 -0
- package/superpowers/skills/diagnosing-superpowers/templates/case.md +64 -0
- package/superpowers/skills/diagnosing-superpowers/templates/issue.md +51 -0
- package/superpowers/skills/diagnosing-superpowers/templates/report.md +82 -0
- package/superpowers/skills/executing-plans/SKILL.md +350 -41
- package/superpowers/skills/executing-plans/scripts/task-done +52 -0
- package/superpowers/skills/executing-plans/scripts/task-start +28 -0
- package/superpowers/skills/requesting-code-review/SKILL.md +1 -1
- package/superpowers/skills/requesting-code-review/code-reviewer.md +17 -0
- package/superpowers/skills/subagent-driven-development/SKILL.md +18 -18
- package/superpowers/skills/subagent-driven-development/re-review-prompt.md +1 -1
- package/superpowers/skills/subagent-driven-development/scripts/review-package +53 -46
- package/superpowers/skills/subagent-driven-development/scripts/sdd-workspace +82 -40
- package/superpowers/skills/subagent-driven-development/scripts/task-brief +43 -41
- package/superpowers/skills/subagent-driven-development/task-reviewer-prompt.md +2 -2
- package/superpowers/skills/systematic-debugging/root-cause-tracing.md +1 -1
- package/superpowers/skills/test-driven-development/SKILL.md +10 -0
- package/superpowers/skills/using-superpowers/SKILL.md +2 -0
- package/superpowers/skills/using-superpowers/references/claude-code-tools.md +29 -0
- package/superpowers/skills/using-superpowers/references/muse-tools.md +35 -0
- package/superpowers/skills/writing-plans/SKILL.md +30 -9
- package/superpowers/skills/writing-skills/SKILL.md +4 -2
- package/superpowers/skills/writing-skills/graphviz-conventions.dot +171 -171
- package/cbb/worktrees/commands/worktree-close.md +0 -64
- package/cbb/worktrees/commands/worktree-init.md +0 -51
- package/cbb/worktrees/commands/worktree-push.md +0 -42
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 跨平台 .tar.gz 解压工具。
|
|
5
|
+
*
|
|
6
|
+
* 用 zlib + 手写 tar parser,避开 OS shell tar(Windows 上是 bsdtar、行为不完全一致),
|
|
7
|
+
* 与仓库「脚本语言统一 Node.js、禁止平台绑定脚本」的约定一致。
|
|
8
|
+
*
|
|
9
|
+
* 支持:
|
|
10
|
+
* - gzip 压缩的 tar(GitHub tarball / npm registry tgz 都是这种)
|
|
11
|
+
* - 目录项(type '5' 或名字以 '/' 结尾)
|
|
12
|
+
* - 跳过 pax 扩展头('g'/'x')等元数据伪条目
|
|
13
|
+
* - 路径穿越防护(拒绝 ../ 逃逸 destDir)
|
|
14
|
+
*
|
|
15
|
+
* 不支持(上游 tarball 均不使用,遇到时跳过而非静默损坏):
|
|
16
|
+
* - GNU longname/longlink('L'/'K')—— 超过 100 字符的路径
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
const zlib = require('zlib');
|
|
22
|
+
|
|
23
|
+
// 非文件、非目录的 tar 条目类型(pax 扩展头、GNU 长名等)
|
|
24
|
+
const META_TYPE_FLAGS = new Set(['g', 'x', 'L', 'K', 'V', 'N']);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 解析 tar buffer,返回条目列表(不落盘)。
|
|
28
|
+
* @param {Buffer} data - 已解压的 tar 数据
|
|
29
|
+
* @returns {Array<{name: string, size: number, typeFlag: string, dataStart: number}>}
|
|
30
|
+
*/
|
|
31
|
+
function listTar(data) {
|
|
32
|
+
const entries = [];
|
|
33
|
+
let off = 0;
|
|
34
|
+
while (off + 512 <= data.length) {
|
|
35
|
+
const header = data.subarray(off, off + 512);
|
|
36
|
+
off += 512;
|
|
37
|
+
|
|
38
|
+
// 连续两个 512 字节零块 = tar 终止
|
|
39
|
+
if (header.every(b => b === 0)) break;
|
|
40
|
+
|
|
41
|
+
// 文件名 header[0..100](NUL 终止)
|
|
42
|
+
let nameEnd = 0;
|
|
43
|
+
while (nameEnd < 100 && header[nameEnd] !== 0) nameEnd++;
|
|
44
|
+
const name = header.subarray(0, nameEnd).toString('utf-8');
|
|
45
|
+
|
|
46
|
+
// 文件大小 header[124..136](八进制 ASCII)
|
|
47
|
+
const size = parseInt(header.subarray(124, 136).toString('utf-8').trim(), 8) || 0;
|
|
48
|
+
|
|
49
|
+
// 类型标志 header[156]('0'/NUL = 普通文件,'5' = 目录)
|
|
50
|
+
const typeFlag = String.fromCharCode(header[156]);
|
|
51
|
+
|
|
52
|
+
entries.push({ name, size, typeFlag, dataStart: off });
|
|
53
|
+
off += Math.ceil(size / 512) * 512;
|
|
54
|
+
}
|
|
55
|
+
return entries;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 解压 .tar.gz buffer 到目标目录。
|
|
60
|
+
* @param {Buffer} compressed - .tar.gz 文件内容
|
|
61
|
+
* @param {string} destDir - 解压目标根目录(不存在则创建)
|
|
62
|
+
* @returns {{files: number, dirs: number}} 落盘计数
|
|
63
|
+
*/
|
|
64
|
+
function extractTarGz(compressed, destDir) {
|
|
65
|
+
const data = zlib.gunzipSync(compressed);
|
|
66
|
+
const root = path.resolve(destDir);
|
|
67
|
+
let files = 0;
|
|
68
|
+
let dirs = 0;
|
|
69
|
+
|
|
70
|
+
for (const entry of listTar(data)) {
|
|
71
|
+
if (!entry.name) continue;
|
|
72
|
+
// 跳过 pax 扩展头等元数据伪条目
|
|
73
|
+
if (META_TYPE_FLAGS.has(entry.typeFlag)) continue;
|
|
74
|
+
|
|
75
|
+
const target = path.resolve(root, entry.name);
|
|
76
|
+
// 路径穿越防护:解压目标必须落在 destDir 内
|
|
77
|
+
if (target !== root && !target.startsWith(root + path.sep)) continue;
|
|
78
|
+
|
|
79
|
+
if (entry.typeFlag === '5' || entry.name.endsWith('/')) {
|
|
80
|
+
fs.mkdirSync(target, { recursive: true });
|
|
81
|
+
dirs += 1;
|
|
82
|
+
} else {
|
|
83
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
84
|
+
fs.writeFileSync(target, data.subarray(entry.dataStart, entry.dataStart + entry.size));
|
|
85
|
+
files += 1;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return { files, dirs };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = { extractTarGz, listTar };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 上游内容拉取的共享工具。
|
|
5
|
+
*
|
|
6
|
+
* 维护者侧(cbbm update --upstream-*)需要访问 GitHub / npm registry。
|
|
7
|
+
* 本模块提供:
|
|
8
|
+
* - 镜像前缀列表加载(config/upstream-mirrors.json,仓库内维护)
|
|
9
|
+
* - 带重定向跟随的 HTTPS GET
|
|
10
|
+
* - 「按镜像列表依次尝试」的拉取策略(首个成功即用)
|
|
11
|
+
*
|
|
12
|
+
* 镜像前缀为空字符串表示直连;列表末位通常放 "" 作为兜底。
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const https = require('https');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
|
|
19
|
+
const SHARED_STANDARDS_DIR = path.join(__dirname, '..', '..', '..');
|
|
20
|
+
const MIRRORS_FILE = path.join(SHARED_STANDARDS_DIR, 'config', 'upstream-mirrors.json');
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 读取指定上游的镜像前缀列表。
|
|
24
|
+
* 文件缺失 / 解析失败 / 未配置该上游时返回 [''](仅直连)。
|
|
25
|
+
* @param {string} name - 上游名(如 'superpowers')
|
|
26
|
+
* @returns {string[]}
|
|
27
|
+
*/
|
|
28
|
+
function loadMirrors(name) {
|
|
29
|
+
try {
|
|
30
|
+
const data = JSON.parse(fs.readFileSync(MIRRORS_FILE, 'utf-8'));
|
|
31
|
+
const list = data[name];
|
|
32
|
+
if (Array.isArray(list) && list.length > 0) {
|
|
33
|
+
return list.map(m => String(m));
|
|
34
|
+
}
|
|
35
|
+
} catch (_) { /* 回退到直连 */ }
|
|
36
|
+
return [''];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 发起 HTTPS GET,跟随重定向(最多 8 次),返回响应体 Buffer。
|
|
41
|
+
* @param {string} url
|
|
42
|
+
* @param {number} [redirectCount]
|
|
43
|
+
* @returns {Promise<Buffer>}
|
|
44
|
+
*/
|
|
45
|
+
function httpsGetBuffer(url, redirectCount = 0) {
|
|
46
|
+
if (redirectCount > 8) return Promise.reject(new Error('Too many redirects'));
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
https.get(url, { headers: { 'User-Agent': 'cbb-cli' } }, res => {
|
|
49
|
+
if ([301, 302, 307, 308].includes(res.statusCode) && res.headers.location) {
|
|
50
|
+
res.resume();
|
|
51
|
+
return httpsGetBuffer(res.headers.location, redirectCount + 1).then(resolve, reject);
|
|
52
|
+
}
|
|
53
|
+
if (res.statusCode !== 200) {
|
|
54
|
+
res.resume();
|
|
55
|
+
return reject(new Error(`HTTP ${res.statusCode}: ${url}`));
|
|
56
|
+
}
|
|
57
|
+
const chunks = [];
|
|
58
|
+
res.on('data', c => chunks.push(c));
|
|
59
|
+
res.on('end', () => resolve(Buffer.concat(chunks)));
|
|
60
|
+
res.on('error', reject);
|
|
61
|
+
}).on('error', reject);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 按镜像列表依次尝试拉取,首个成功即返回。
|
|
67
|
+
* @param {string[]} mirrors - 镜像前缀列表('' = 直连)
|
|
68
|
+
* @param {(prefix: string) => string} buildUrl - 由前缀构造完整 URL
|
|
69
|
+
* @returns {Promise<{buffer: Buffer, mirror: string, url: string}>}
|
|
70
|
+
*/
|
|
71
|
+
async function fetchWithMirrors(mirrors, buildUrl) {
|
|
72
|
+
const errors = [];
|
|
73
|
+
for (const mirror of mirrors) {
|
|
74
|
+
const url = buildUrl(mirror);
|
|
75
|
+
try {
|
|
76
|
+
const buffer = await httpsGetBuffer(url);
|
|
77
|
+
return { buffer, mirror, url };
|
|
78
|
+
} catch (err) {
|
|
79
|
+
errors.push(`${mirror || '(直连)'}: ${err.message}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
throw new Error(`所有镜像均失败:\n ${errors.join('\n ')}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = {
|
|
86
|
+
MIRRORS_FILE,
|
|
87
|
+
loadMirrors,
|
|
88
|
+
httpsGetBuffer,
|
|
89
|
+
fetchWithMirrors,
|
|
90
|
+
};
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: cbb-worktree-close
|
|
3
3
|
description: "关闭/清理/删除需求工作空间的 worktree 和分支。触发:用户明确说关闭/清理/删除 worktree、结束隔离开发、合并完要清理。禁止在 cbb-worktree-init 执行后自动调用——只有用户主动要求关闭时才触发。"
|
|
4
|
-
license: MIT
|
|
5
|
-
compatibility: Requires git (>=2.30) and Node.js (>=16.7.0). All commands are pure Node.js scripts under `scripts/`, fully cross-platform (Windows / macOS / Linux, bash / PowerShell / Git Bash).
|
|
6
|
-
metadata:
|
|
7
|
-
author: Contributors
|
|
8
|
-
version: "2.0"
|
|
9
|
-
generatedBy: coding-bb
|
|
10
4
|
---
|
|
11
5
|
|
|
12
6
|
# Close Worktree(跨平台脚本化版本 v2.0)
|
|
@@ -29,12 +23,28 @@ metadata:
|
|
|
29
23
|
|
|
30
24
|
---
|
|
31
25
|
|
|
26
|
+
## 调用脚本的统一方式
|
|
27
|
+
|
|
28
|
+
**重要**:本技能的所有脚本位于**本 SKILL.md 所在目录的 `scripts/` 下**(skill 目录)。
|
|
29
|
+
|
|
30
|
+
调用时用**本技能目录的绝对路径**拼接脚本名(AI 读取本 SKILL.md 时即已知该目录),例如:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
node "<skill_dir>/scripts/<name>.js" <参数>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
> - `<skill_dir>` = 本 SKILL.md 所在目录的绝对路径(AI 从读取路径即可推出)
|
|
37
|
+
> - **cwd 必须保持在工作空间根目录**:脚本按 cwd 定位 `workspace-config.json` 与 git 仓库
|
|
38
|
+
> - 不要用 `./scripts/...` 裸相对路径(AI 的 cwd 不是技能目录,会解析失败)
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
32
42
|
## Step 0:前置校验
|
|
33
43
|
|
|
34
44
|
### 0.1 环境健康探活
|
|
35
45
|
|
|
36
46
|
```bash
|
|
37
|
-
node "
|
|
47
|
+
node "<skill_dir>/scripts/check-env.js"
|
|
38
48
|
```
|
|
39
49
|
|
|
40
50
|
**解读输出**:
|
|
@@ -45,7 +55,7 @@ node "D:/workspace/<ws_root>/.qoder/skills/cbb-worktree-close/scripts/check-env.
|
|
|
45
55
|
### 0.2 定位工作空间根目录
|
|
46
56
|
|
|
47
57
|
```bash
|
|
48
|
-
node "
|
|
58
|
+
node "<skill_dir>/scripts/find-workspace-root.js"
|
|
49
59
|
```
|
|
50
60
|
|
|
51
61
|
**解读输出**:
|
|
@@ -69,7 +79,7 @@ node "D:/workspace/<ws_root>/.qoder/skills/cbb-worktree-close/scripts/find-works
|
|
|
69
79
|
3. AI agent 列出 `.worktrees/` 下的所有 worktree:
|
|
70
80
|
|
|
71
81
|
```bash
|
|
72
|
-
node "
|
|
82
|
+
node "<skill_dir>/scripts/find-target-worktree.js "<ws_root>"
|
|
73
83
|
```
|
|
74
84
|
|
|
75
85
|
> 注:本技能支持工作空间根目录或 worktree 内执行,find-target-worktree 自动从 cwd 反推或列出。
|
|
@@ -84,7 +94,7 @@ node "D:/workspace/<ws_root>/.qoder/skills/cbb-worktree-close/scripts/find-targe
|
|
|
84
94
|
### 1.2 发现关联应用 worktree
|
|
85
95
|
|
|
86
96
|
```bash
|
|
87
|
-
node "
|
|
97
|
+
node "<skill_dir>/scripts/discover-apps.js "<ws_root>" "<branch>"
|
|
88
98
|
```
|
|
89
99
|
|
|
90
100
|
脚本自动从 `workspace-config.json` 读取应用列表,扫描 `.worktrees/worktree-<branch>/<应用名>/` 实际存在的子目录。
|
|
@@ -93,12 +103,12 @@ node "D:/workspace/<ws_root>/.qoder/skills/cbb-worktree-close/scripts/discover-a
|
|
|
93
103
|
- `workspace` 字段:workspace worktree 绝对路径(AI agent 记录供后续命令使用)
|
|
94
104
|
- `apps` 数组:实际存在的应用 worktree 列表(每项含 `name`, `repo`, `side`, `wt_path`, `app_path`)
|
|
95
105
|
|
|
96
|
-
> 说明:app worktree 物理嵌套在 workspace worktree 内,移除 workspace worktree 时嵌套的子目录会一并消失,但各 app 仓库的 git worktree 元数据(`.git/worktrees/`)仍需通过 `git worktree remove` 逐个清理。AI
|
|
106
|
+
> 说明:app worktree 物理嵌套在 workspace worktree 内,移除 workspace worktree 时嵌套的子目录会一并消失,但各 app 仓库的 git worktree 元数据(`.git/worktrees/`)仍需通过 `git worktree remove` 逐个清理。AI 配置只装在业务空间根(单层安装),worktree 内没有 AI 配置目录,无需特殊处理。
|
|
97
107
|
|
|
98
108
|
### 1.3 检查未归档 change
|
|
99
109
|
|
|
100
110
|
```bash
|
|
101
|
-
node "
|
|
111
|
+
node "<skill_dir>/scripts/check-unarchived.js "<ws_root>"
|
|
102
112
|
```
|
|
103
113
|
|
|
104
114
|
脚本扫描 `openspec/changes/` 目录,筛选出非 `archive/` 子目录的 change 名称列表。
|
|
@@ -117,7 +127,7 @@ node "D:/workspace/<ws_root>/.qoder/skills/cbb-worktree-close/scripts/check-unar
|
|
|
117
127
|
对 workspace worktree 和每个关联应用 worktree,依次执行三项检查并汇总成安全状态表。**这是破坏性操作前的必经环节,结果将驱动 Step 3 的用户决策。**
|
|
118
128
|
|
|
119
129
|
```bash
|
|
120
|
-
node "
|
|
130
|
+
node "<skill_dir>/scripts/safety-check.js "<ws_root>" "<branch>"
|
|
121
131
|
```
|
|
122
132
|
|
|
123
133
|
脚本内部对每个仓库自动执行:
|
|
@@ -226,10 +236,10 @@ AskUserQuestion({
|
|
|
226
236
|
|
|
227
237
|
```bash
|
|
228
238
|
# 仅删本地分支(已合并自动 -d,未合并自动 -D;含 squash 合并 -D 兜底)
|
|
229
|
-
node "<
|
|
239
|
+
node "<skill_dir>/scripts/delete-branches.js" "<ws_root>" "<branch>" local
|
|
230
240
|
|
|
231
241
|
# 删本地 + 远程分支
|
|
232
|
-
node "<
|
|
242
|
+
node "<skill_dir>/scripts/delete-branches.js" "<ws_root>" "<branch>" local_remote
|
|
233
243
|
```
|
|
234
244
|
|
|
235
245
|
**调用前的强警告**(调用方需自评):
|
|
@@ -260,7 +270,7 @@ node "<skill>/scripts/delete-branches.js" "<ws_root>" "<branch>" local_remote
|
|
|
260
270
|
[中止] 终止流程,我先手动处理
|
|
261
271
|
```
|
|
262
272
|
|
|
263
|
-
- 用户选择「先推送」→ AI 引导用户执行 `cbb-worktree-push
|
|
273
|
+
- 用户选择「先推送」→ AI 引导用户执行 `cbb-worktree-push` skill(或 `/cbb-worktree-push`),完成后**重新跑** Step 2 safety-check 确认 `unpushed_count = 0` 再回到本步骤
|
|
264
274
|
- 用户选择「接受丢失」→ 必须二次确认(ahead 提交真的会丢),确认后继续 Step 4
|
|
265
275
|
- 用户选择「中止」→ 终止流程
|
|
266
276
|
|
|
@@ -289,14 +299,14 @@ node "<skill>/scripts/delete-branches.js" "<ws_root>" "<branch>" local_remote
|
|
|
289
299
|
**顺序:先移除各应用 worktree,再移除 workspace worktree**(逆序于创建;也为后续删分支扫清"分支仍被检出"的障碍)。
|
|
290
300
|
|
|
291
301
|
```bash
|
|
292
|
-
node "
|
|
302
|
+
node "<skill_dir>/scripts/remove-worktrees.js "<ws_root>" "<branch>" "<force>"
|
|
293
303
|
```
|
|
294
304
|
|
|
295
305
|
**三层降级策略**:`git worktree remove` 实际包含两个动作——①清理 git 元数据(`.git/worktrees/`)②删除物理目录。当 IDE 占用目录时 ② 会被 Windows 文件锁阻塞,但 ① 永远不受影响。脚本将这两个动作解耦:
|
|
296
306
|
|
|
297
307
|
- **第 1 层(正常路径):** `git worktree remove` 一步完成元数据 + 物理目录清理
|
|
298
308
|
- **第 2 层(解耦清理):** 当目录被锁时,先 `prune` 清理 git 元数据(始终成功),再 `rmdir`/`rm -rf` 尝试物理删除
|
|
299
|
-
- **第 3 层(延迟清理):** Windows 下 IDE
|
|
309
|
+
- **第 3 层(延迟清理):** Windows 下 IDE 持有排他句柄时触发。元数据已清理保证分支删除不受影响;物理目录**不会被 init 自动删除**(0.0.3-beta.11 起 init 只 prune 元数据、绝不删目录,防误删活跃 worktree),需用户关闭 IDE 后手动删除,或重跑 close 补删
|
|
300
310
|
|
|
301
311
|
**解读末行 `REMOVE_RESULT`**:
|
|
302
312
|
- `has_locked: false` → 全部清理成功
|
|
@@ -333,9 +343,9 @@ node "D:/workspace/<ws_root>/.qoder/skills/cbb-worktree-close/scripts/remove-wor
|
|
|
333
343
|
如用户在 Step 1.1 选定 worktree 时已要求"删除分支",或运行后需要单独清理,可在此步骤**手动调用**:
|
|
334
344
|
|
|
335
345
|
```bash
|
|
336
|
-
node "<
|
|
346
|
+
node "<skill_dir>/scripts/delete-branches.js" "<ws_root>" "<branch>" local
|
|
337
347
|
# 或
|
|
338
|
-
node "<
|
|
348
|
+
node "<skill_dir>/scripts/delete-branches.js" "<ws_root>" "<branch>" local_remote
|
|
339
349
|
```
|
|
340
350
|
|
|
341
351
|
脚本内部对每个仓库:
|
|
@@ -388,7 +398,6 @@ git -C "<ws_root>" worktree list
|
|
|
388
398
|
⏭️ fix-payment-timeout → 用户选择跳过
|
|
389
399
|
(无未归档 change 时不显示本节)
|
|
390
400
|
分支处理:feature-xxx 已保留(默认行为;如需删除分支,手动调用 scripts/delete-branches.js)
|
|
391
|
-
AI 配置:worktree 内独立安装,随 worktree 删除而清理;工作空间根目录的 AI 配置保留
|
|
392
401
|
剩余工作空间 worktree:<列出仍存在的,或 "无" >
|
|
393
402
|
Workspace cleaned up.
|
|
394
403
|
```
|
|
@@ -411,7 +420,7 @@ git -C "<ws_root>" worktree list
|
|
|
411
420
|
- 未经用户确认用 `--force` 丢弃未提交修改,或主动调用 `delete-branches.js` 对未合并分支执行 `-D` 强删
|
|
412
421
|
- 操作 `.worktrees/worktree-<需求名>` 以外的任何 worktree(如 `~/.qoder/worktree/...` 会话 worktree)
|
|
413
422
|
- 移除/删除前跳过 Step 2 安全评估
|
|
414
|
-
- 删除工作空间根目录的 AI 配置目录(`.claude/`、`.qoder/`、`.
|
|
423
|
+
- 删除工作空间根目录的 AI 配置目录(`.claude/`、`.qoder/`、`.opencode/` 等)
|
|
415
424
|
- 用 `AskUserQuestion` 收集自由文本(分支名等直接对话提问)
|
|
416
425
|
- 手动改任何仓库的 `.gitignore`(关闭流程不碰它)
|
|
417
426
|
- 运行环境 node 不可用时强行继续 → 改为 fail-fast 提示用户
|
|
@@ -429,7 +438,7 @@ git -C "<ws_root>" worktree list
|
|
|
429
438
|
- **合并判定依赖刷新后的远程引用,故 Step 2.0 先主动 `git fetch`**:feature 多由他人经 MR 合入远端,本地引用否则会陈旧误判
|
|
430
439
|
- **并行块用临时文件收结果、不用共享变量**:原 bash 版本中 `&` 后台任务跑在独立子 shell;Node.js 同步执行无此问题
|
|
431
440
|
- **`.gitignore` 无需清理**:创建时的 `.worktrees/` 条目提交在 feature 分支上,删分支时随分支消失(未合并)或无害保留(已合并)
|
|
432
|
-
- **AI
|
|
441
|
+
- **AI 配置只装在空间根(单层安装)**:worktree 内没有 AI 配置目录,关闭时无需处理
|
|
433
442
|
- **部分 `worktree remove` 失败**:已移除的无需回退,未移除的报告详情让用户处理后重跑
|
|
434
443
|
- **Windows IDE 目录锁的三层降级**:`git worktree remove` 内部做两件事——①清元数据 ②删目录。Windows 文件锁只阻塞 ②,不阻塞 ①
|
|
435
444
|
|
|
@@ -78,7 +78,7 @@ function removeWt(repoPath, wtPath, name) {
|
|
|
78
78
|
|
|
79
79
|
// 第 3 层:目录被锁
|
|
80
80
|
lockedDirs.push(wtPath);
|
|
81
|
-
console.log('⏳ ' + name + '
|
|
81
|
+
console.log('⏳ ' + name + ' 元数据已清理,物理目录被锁定(init 不会自动删除目录,需手动清理)');
|
|
82
82
|
results.push({ name, status: 'locked' });
|
|
83
83
|
}
|
|
84
84
|
|
|
@@ -110,7 +110,7 @@ if (lockedDirs.length > 0) {
|
|
|
110
110
|
console.log('');
|
|
111
111
|
console.log('⏳ 以下目录被外部进程占用(IDE 等),git 元数据已清理,物理目录仍保留:');
|
|
112
112
|
lockedDirs.forEach(d => console.log(' - ' + d));
|
|
113
|
-
console.log(' → 请关闭占用这些目录的程序(如 IDE
|
|
113
|
+
console.log(' → 请关闭占用这些目录的程序(如 IDE)后手动删除(init 不再自动清理物理目录)');
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
console.log('---');
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: cbb-worktree-init
|
|
3
3
|
description: "为一个需求在 workspace 与各关联应用仓库(= workspace-config.json 全量 apps)同步创建同名物理 git worktree,直聚在 `.worktrees/worktree-<需求名>/` 下。触发:用户明确要求创建需求工作空间/开发隔离环境,或为已有需求补齐新增应用(重跑本技能即幂等)。禁止自动串联 cbb-worktree-close——除非用户明确说了要关闭。"
|
|
4
|
-
license: MIT
|
|
5
|
-
compatibility: Requires git (>=2.30) and Node.js (>=16.7.0). All commands are pure Node.js scripts under `scripts/`, fully cross-platform (Windows / macOS / Linux, bash / PowerShell / Git Bash).
|
|
6
|
-
metadata:
|
|
7
|
-
author: Contributors
|
|
8
|
-
version: "2.0"
|
|
9
|
-
generatedBy: coding-bb
|
|
10
4
|
---
|
|
11
5
|
|
|
12
6
|
# Init Worktree(跨平台脚本化版本 v2.0)
|
|
@@ -28,13 +22,18 @@ metadata:
|
|
|
28
22
|
|
|
29
23
|
## 调用脚本的统一方式
|
|
30
24
|
|
|
31
|
-
|
|
25
|
+
**重要**:本技能的所有脚本位于**本 SKILL.md 所在目录的 `scripts/` 下**(skill 目录)。
|
|
32
26
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
调用时用**本技能目录的绝对路径**拼接脚本名(AI 读取本 SKILL.md 时即已知该目录),例如:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
node "<skill_dir>/scripts/<name>.js" <参数>
|
|
36
31
|
```
|
|
37
32
|
|
|
33
|
+
> - `<skill_dir>` = 本 SKILL.md 所在目录的绝对路径(AI 从读取路径即可推出)
|
|
34
|
+
> - **cwd 必须保持在工作空间根目录**:脚本按 cwd 定位 `workspace-config.json` 与 git 仓库
|
|
35
|
+
> - 不要用 `./scripts/...` 裸相对路径(AI 的 cwd 不是技能目录,会解析失败)
|
|
36
|
+
|
|
38
37
|
脚本输出:
|
|
39
38
|
- 人类可读报告(OK / FAIL / WARN)
|
|
40
39
|
- 机器可读摘要(末行以 XXX_OK / XXX_FAILED / XXX_RESULT 开头)
|
|
@@ -54,7 +53,7 @@ node "D:\workspace\<用户工作空间>\.qoder\skills\cbb-worktree-init\scripts\
|
|
|
54
53
|
### 0.1 环境健康探活
|
|
55
54
|
|
|
56
55
|
```bash
|
|
57
|
-
node "
|
|
56
|
+
node "<skill_dir>/scripts/check-env.js"
|
|
58
57
|
```
|
|
59
58
|
|
|
60
59
|
**解读输出**:
|
|
@@ -67,7 +66,7 @@ node "D:/workspace/<ws_root>/.qoder/skills/cbb-worktree-init/scripts/check-env.j
|
|
|
67
66
|
提前检测权限/版本问题,把可能在后续步骤暴露的失败前移到流程最开始:
|
|
68
67
|
|
|
69
68
|
```bash
|
|
70
|
-
node "
|
|
69
|
+
node "<skill_dir>/scripts/check-env-deep.js"
|
|
71
70
|
```
|
|
72
71
|
|
|
73
72
|
**解读输出**:
|
|
@@ -81,7 +80,7 @@ node "D:/workspace/<ws_root>/.qoder/skills/cbb-worktree-init/scripts/check-env-d
|
|
|
81
80
|
### 0.2 读取并校验 workspace-config.json
|
|
82
81
|
|
|
83
82
|
```bash
|
|
84
|
-
node "
|
|
83
|
+
node "<skill_dir>/scripts/parse-config.js"
|
|
85
84
|
```
|
|
86
85
|
|
|
87
86
|
**解读输出**:
|
|
@@ -148,7 +147,7 @@ worktree 需要知道要关联哪些应用仓库。
|
|
|
148
147
|
|
|
149
148
|
```bash
|
|
150
149
|
# ws_root 来自 AI agent 维护的状态
|
|
151
|
-
node "
|
|
150
|
+
node "<skill_dir>/scripts/sync-repos.js" "<ws_root>"
|
|
152
151
|
```
|
|
153
152
|
|
|
154
153
|
**用法**:
|
|
@@ -192,7 +191,7 @@ node "D:/workspace/<ws_root>/.qoder/skills/cbb-worktree-init/scripts/sync-repos.
|
|
|
192
191
|
| 当前在主分支上 | ✅ | ❌ |
|
|
193
192
|
|
|
194
193
|
```bash
|
|
195
|
-
node "
|
|
194
|
+
node "<skill_dir>/scripts/check-repos.js" "<ws_root>"
|
|
196
195
|
```
|
|
197
196
|
|
|
198
197
|
**解读输出**:
|
|
@@ -220,21 +219,23 @@ node "D:/workspace/<ws_root>/.qoder/skills/cbb-worktree-init/scripts/check-repos
|
|
|
220
219
|
### 3.2 安全更新 .gitignore(前置)
|
|
221
220
|
|
|
222
221
|
```bash
|
|
223
|
-
node "
|
|
222
|
+
node "<skill_dir>/scripts/update-gitignore.js" "<ws_root>" "<branch>"
|
|
224
223
|
```
|
|
225
224
|
|
|
226
|
-
脚本内部自动完成:stash 保护 → 切分支 → 添加 `.worktrees/` 和 `.codespace/` → 提交 → 切回 → pop stash
|
|
225
|
+
脚本内部自动完成:stash 保护 → 分支不存在则从 HEAD 创建 → 切分支 → 添加 `.worktrees/` 和 `.codespace/` → 提交 → 切回 → pop stash
|
|
227
226
|
|
|
228
227
|
**解读输出**:
|
|
229
228
|
- 退出码 0 + 末行 `GITIGNORE_OK` → 成功
|
|
230
229
|
- 退出码 2 → 执行失败,**终止流程**
|
|
231
230
|
|
|
232
|
-
>
|
|
231
|
+
> ⓘ **分支自动创建**:本步骤先于 Step 3.3 执行,workspace 分支此时不存在,脚本会自动从 HEAD 创建(与 `create-branches.js` 对 workspace 的行为一致,Step 3.3 报 `reused`)。`.gitignore` 提交正好落在需求分支上,由 Step 3.4 push 一并推送。
|
|
232
|
+
|
|
233
|
+
> ⚠️ 必须在提交后切回主分支,否则 Step 3.5 的 `git worktree add` 会因分支已检出而失败(脚本已自动处理)。
|
|
233
234
|
|
|
234
235
|
### 3.3 创建分支
|
|
235
236
|
|
|
236
237
|
```bash
|
|
237
|
-
node "
|
|
238
|
+
node "<skill_dir>/scripts/create-branches.js" "<ws_root>" "<branch>"
|
|
238
239
|
```
|
|
239
240
|
|
|
240
241
|
**解读输出**:
|
|
@@ -259,7 +260,7 @@ node "D:/workspace/<ws_root>/.qoder/skills/cbb-worktree-init/scripts/create-bran
|
|
|
259
260
|
### 3.4 推送并建立 upstream
|
|
260
261
|
|
|
261
262
|
```bash
|
|
262
|
-
node "
|
|
263
|
+
node "<skill_dir>/scripts/push-branches.js" "<ws_root>" "<branch>"
|
|
263
264
|
```
|
|
264
265
|
|
|
265
266
|
脚本内部自动完成:
|
|
@@ -284,20 +285,20 @@ node "D:/workspace/<ws_root>/.qoder/skills/cbb-worktree-init/scripts/push-branch
|
|
|
284
285
|
|
|
285
286
|
**为什么 push 放在 worktree 之前:** 失败早暴露,无 worktree 损失;成功后再创建 worktree,分支天然有 upstream;独立脚本便于单测。
|
|
286
287
|
|
|
287
|
-
### 3.5 创建 Worktree
|
|
288
|
+
### 3.5 创建 Worktree
|
|
288
289
|
|
|
289
290
|
```bash
|
|
290
|
-
node "
|
|
291
|
+
node "<skill_dir>/scripts/create-worktrees.js" "<ws_root>" "<branch>"
|
|
291
292
|
```
|
|
292
293
|
|
|
293
294
|
脚本内部自动完成:
|
|
294
|
-
1.
|
|
295
|
+
1. **元数据自愈**:仅 `git worktree prune`(清理"目录已缺失/失效"的注册项);**绝不删除任何物理目录**(0.0.3-beta.11 起——旧版"清理无元数据残留目录"因 Windows 路径分隔符不匹配会误删活跃 worktree,已整体移除)
|
|
295
296
|
2. **workspace worktree 创建**:`git worktree add .worktrees/worktree-<branch> <branch>`
|
|
296
297
|
3. **app worktree 创建**(物理嵌套在 workspace worktree 内)
|
|
297
298
|
|
|
298
299
|
**解读输出**:
|
|
299
300
|
- 退出码 0 + 末行 `WT_PATH <绝对路径>` → 全部成功,AI 解析出 worktree 绝对路径供后续 Step 4 使用
|
|
300
|
-
- 退出码 2 → workspace worktree
|
|
301
|
+
- 退出码 2 → workspace worktree 创建失败(分支已检出/权限不足),或目标目录存在但非有效 worktree(上次 close 被 IDE 锁残留)——**终止流程**,按脚本提示引导用户跑 `cbb-worktree-close` 或手动删除该目录 + `git worktree prune` 后重跑
|
|
301
302
|
- 退出码 3 → 部分 app worktree 创建失败,**报告已创建/失败详情,用户需手动清理**
|
|
302
303
|
|
|
303
304
|
**报告示例**:
|
|
@@ -322,6 +323,7 @@ WT_PATH d:\workspace\xxx\.worktrees\worktree-feature-login
|
|
|
322
323
|
1. **继续在当前会话开发**:本技能不打开新窗口;需求工作目录为 `<wt_path>`
|
|
323
324
|
2. **目录纪律**:本会话后续所有产生写操作的命令(git commit / openspec / 构建 / 测试等)必须以 `<wt_path>`(或其应用子目录)为工作目录(显式 `cd` 或绝对路径);**禁止在空间根(主分支)执行任何写操作**
|
|
324
325
|
3. 不自动安装依赖:各仓库技术栈不一(Maven 依赖本机 `~/.m2` 全局共享、无需初始化;Node/Python 等由你进入 worktree 后执行正确的命令,如 `npm ci` / `pnpm`)
|
|
326
|
+
4. **输出下一步建议**:最终报告必须包含「下一步建议」段(格式见「最终报告」),告知用户后续可走的流程;仅提示,不自动串联执行任何后续技能
|
|
325
327
|
|
|
326
328
|
---
|
|
327
329
|
|
|
@@ -333,6 +335,12 @@ WT_PATH d:\workspace\xxx\.worktrees\worktree-feature-login
|
|
|
333
335
|
✅ worktree 已创建:<wt_path>
|
|
334
336
|
关联应用:<应用名 → 嵌套路径> 列表
|
|
335
337
|
📌 继续在当前会话开发:后续写操作命令一律以上述目录为工作目录
|
|
338
|
+
👉 下一步建议:
|
|
339
|
+
1. 开始需求开发(二选一):
|
|
340
|
+
- 走 OpenSpec 工作流:/opsx:propose → /opsx:apply → /opsx:verify → /opsx:archive
|
|
341
|
+
- 或直接对话开发:把需求告诉我,在 <wt_path> 内实现
|
|
342
|
+
2. 开发中提交推送:说"提交并推送"(cbb-worktree-push)
|
|
343
|
+
3. 需求完成后:说"关闭工作空间"(cbb-worktree-close)
|
|
336
344
|
```
|
|
337
345
|
|
|
338
346
|
> 若 Step 3.5 为部分失败(退出码 3),报告必须列出已创建/失败详情与回滚清理命令。
|
|
@@ -347,7 +355,7 @@ WT_PATH d:\workspace\xxx\.worktrees\worktree-feature-login
|
|
|
347
355
|
|------|----------|
|
|
348
356
|
| 空间新增了应用(编辑 `workspace-config.json` 后) | Step 1 拉取新应用代码 → Step 3.3 为新应用建分支 → Step 3.4 推送(已一致的分支 `skipped`,不一致则阻塞询问)→ Step 3.5 创建缺失的嵌套 worktree |
|
|
349
357
|
| 上次 init 中途失败 | 已存在的分支 / worktree 自动复用,`.gitignore` 处理自动跳过,仅补齐缺失部分 |
|
|
350
|
-
| 应用从 config 移除 | 已创建的分支 / worktree **不会**自动删除;如需清理用
|
|
358
|
+
| 应用从 config 移除 | 已创建的分支 / worktree **不会**自动删除;如需清理用 `cbb-worktree-close` skill 关闭后重建 |
|
|
351
359
|
|
|
352
360
|
> ⓘ 重跑**不会静默推送**开发中的本地提交:远程分支已存在但与本地不一致时,Step 3.4 走阻塞式 `remote-exists` 决策(展示 commit 元信息让用户选择)。
|
|
353
361
|
|
|
@@ -368,7 +376,7 @@ WT_PATH d:\workspace\xxx\.worktrees\worktree-feature-login
|
|
|
368
376
|
| 运行环境 node 不可用 | 脚本 fail-fast 并提示用户到系统终端重试 |
|
|
369
377
|
| push 失败(SSH/HTTPS/网络/保护) | push-branches.js 退出码 2,AI 阻塞并询问用户三选项(修复后重试/跳过失败仓库/完全终止) |
|
|
370
378
|
| 远程分支已存在 | push-branches.js 输出 metadata,AI 展示 commit 元信息后询问两选项(关联/终止+重新创建) |
|
|
371
|
-
| 开发过程中推送所有 worktree | Step 3.4 仅覆盖创建时的空 commit;开发过程中产生的提交必须调
|
|
379
|
+
| 开发过程中推送所有 worktree | Step 3.4 仅覆盖创建时的空 commit;开发过程中产生的提交必须调 `cbb-worktree-push` skill 推送(详见该 skill) |
|
|
372
380
|
|
|
373
381
|
---
|
|
374
382
|
|
|
@@ -467,7 +475,7 @@ WT_PATH d:\workspace\xxx\.worktrees\worktree-feature-login
|
|
|
467
475
|
- workspace 仓库标准检查(当前在主分支等),app 仓库轻量检查(fetch + `origin/main` 可用即可)
|
|
468
476
|
- app 仓库分支从 `origin/main` 创建(`git branch <需求名> ${REMOTE}/${MAIN}`),不依赖本地主分支状态
|
|
469
477
|
- 严格 Step 3.3 → Step 3.4 顺序(先统一创建分支,再统一创建 worktree;workspace 工作树先建,各应用按序补建)
|
|
470
|
-
- 所有脚本调用以 `node "<
|
|
478
|
+
- 所有脚本调用以 `node "<skill_dir>/scripts/<name>.js" "<绝对路径>" "<branch>"` 形式发起(apps 由脚本内部从 workspace-config.json 读取);**执行时 cwd 保持在工作空间根**(`check-env.js` / `parse-config.js` 按 cwd 定位)
|
|
471
479
|
- **Step 3.4 push 步骤必须执行且插入在 create-branches.js (3.3) 之后、create-worktrees.js (3.5) 之前** — 失败时阻塞整个 init-worktree 流程
|
|
472
480
|
- **Step 3.4 仅覆盖创建 worktree 时的空 commit** — 开发过程中产生的本地 commit(如 apply 任务实现)必须由用户在 worktree 中手动提交并调 `cbb-worktree-push` 推送;不允许依赖 Step 3.4 之后的自动推送(不存在)
|
|
473
481
|
|
|
@@ -483,6 +491,6 @@ WT_PATH d:\workspace\xxx\.worktrees\worktree-feature-login
|
|
|
483
491
|
| `check-repos.js` | 状态检查 | ws_root | 0/1/2 |
|
|
484
492
|
| `update-gitignore.js` | 安全更新 .gitignore | ws_root, branch | 0/1/2 |
|
|
485
493
|
| `create-branches.js` | 创建分支 | ws_root, branch | 0/1/2 |
|
|
486
|
-
| `create-worktrees.js` | 创建 worktree
|
|
494
|
+
| `create-worktrees.js` | 创建 worktree(仅 prune 元数据,不删物理目录) | ws_root, branch | 0/1/2/3 |
|
|
487
495
|
| `push-branches.js` | 推送并建立 upstream(已一致则 skipped;带错误归类 + 元信息采集) | ws_root, branch | 0/1/2 |
|
|
488
496
|
| `check-env-deep.js` | 环境健康自检(Node 版本 / 权限) | 无 | 0/2 |
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
require('./silence-popup');
|
|
3
3
|
/**
|
|
4
|
-
* cbb-worktree-init 创建 Worktree
|
|
4
|
+
* cbb-worktree-init 创建 Worktree(自愈清理仅限元数据 prune)
|
|
5
5
|
*
|
|
6
6
|
* 用法:node create-worktrees.js <ws_root> <branch>
|
|
7
7
|
* (apps 由脚本内部从 <ws_root>/workspace-config.json 读取)
|
|
8
8
|
*
|
|
9
9
|
* 行为:
|
|
10
|
-
* 1.
|
|
10
|
+
* 1. 自愈清理:仅 git worktree prune(元数据),**绝不删除任何物理目录**
|
|
11
|
+
* (0.0.3-beta.11 事故修复:旧版按"不在 worktree list"负向推断递归删残留目录,
|
|
12
|
+
* Windows 下 git 输出正斜杠路径与 realpathSync 反斜杠永不匹配,误删活跃 worktree)
|
|
11
13
|
* 2. workspace worktree 创建(.worktrees/worktree-<branch>)
|
|
12
14
|
* 3. 各 app worktree 创建(物理嵌套在 workspace worktree 内)
|
|
13
15
|
* 4. 失败时输出回滚提示
|
|
@@ -15,7 +17,7 @@ require('./silence-popup');
|
|
|
15
17
|
* 退出码:
|
|
16
18
|
* 0 - 全部成功
|
|
17
19
|
* 1 - 参数错误
|
|
18
|
-
* 2 - workspace worktree
|
|
20
|
+
* 2 - workspace worktree 创建失败(含目标目录存在但非有效 worktree 的残留场景)
|
|
19
21
|
* 3 - 部分 app worktree 创建失败
|
|
20
22
|
*/
|
|
21
23
|
|
|
@@ -54,52 +56,28 @@ function run(cmd, cwd) {
|
|
|
54
56
|
}
|
|
55
57
|
}
|
|
56
58
|
|
|
57
|
-
// 1.
|
|
58
|
-
console.log('===
|
|
59
|
+
// 1. 自愈清理(仅元数据:prune 只清理"目录已缺失/失效"的 worktree 注册项,不碰物理目录)
|
|
60
|
+
console.log('=== 自愈清理(仅 prune 元数据,不删物理目录)===');
|
|
59
61
|
|
|
60
|
-
// 1.1 prune 所有仓库
|
|
61
62
|
const allRepos = [wsRoot, ...apps.map(a => path.join(wsRoot, '.codespace', a.name))];
|
|
62
63
|
for (const repo of allRepos) {
|
|
63
64
|
if (fs.existsSync(repo)) {
|
|
64
65
|
run('git worktree prune', repo);
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
|
-
console.log('✅ 已 prune 所有仓库的 worktree
|
|
68
|
-
|
|
69
|
-
// 1.2 清理 .worktrees/ 下无元数据的物理目录
|
|
70
|
-
const worktreesDir = path.join(wsRoot, '.worktrees');
|
|
71
|
-
if (fs.existsSync(worktreesDir)) {
|
|
72
|
-
const validWts = run('git worktree list --porcelain', wsRoot);
|
|
73
|
-
const validPaths = new Set();
|
|
74
|
-
if (validWts.ok) {
|
|
75
|
-
validWts.out.split('\n').forEach(line => {
|
|
76
|
-
if (line.startsWith('worktree ')) {
|
|
77
|
-
validPaths.add(line.substring(9).trim());
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const entries = fs.readdirSync(worktreesDir, { withFileTypes: true });
|
|
83
|
-
for (const entry of entries) {
|
|
84
|
-
if (!entry.isDirectory()) continue;
|
|
85
|
-
const dir = path.join(worktreesDir, entry.name);
|
|
86
|
-
let absDir;
|
|
87
|
-
try { absDir = fs.realpathSync(dir); } catch { continue; }
|
|
88
|
-
if (!validPaths.has(absDir)) {
|
|
89
|
-
try {
|
|
90
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
91
|
-
console.log('🧹 已清理残留目录:' + entry.name);
|
|
92
|
-
} catch (e) {
|
|
93
|
-
console.log('⏳ 残留目录仍被占用,跳过:' + entry.name);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
68
|
+
console.log('✅ 已 prune 所有仓库的 worktree 元数据(物理目录一律保留)');
|
|
98
69
|
|
|
99
70
|
// 2. workspace worktree 创建
|
|
100
71
|
console.log('=== 创建 Workspace Worktree ===');
|
|
101
72
|
const wtDir = path.join(wsRoot, '.worktrees', 'worktree-' + branch);
|
|
102
73
|
if (fs.existsSync(wtDir)) {
|
|
74
|
+
if (!fs.existsSync(path.join(wtDir, '.git'))) {
|
|
75
|
+
console.error('❌ 目标目录已存在但不是有效 worktree(缺 .git 指针):' + wtDir);
|
|
76
|
+
console.error(' 可能是上次 close / init 中断的残留。本脚本不会自动删除任何目录,请二选一后重跑:');
|
|
77
|
+
console.error(' 1) 执行 cbb-worktree-close 技能清理该需求');
|
|
78
|
+
console.error(' 2) 手动删除上述目录后执行:git -C "' + wsRoot + '" worktree prune');
|
|
79
|
+
process.exit(2);
|
|
80
|
+
}
|
|
103
81
|
console.log('⏭️ workspace worktree 已存在,复用:' + wtDir);
|
|
104
82
|
} else {
|
|
105
83
|
const created = run('git worktree add ".worktrees/worktree-' + branch + '" "' + branch + '"', wsRoot);
|