@jspg-ai/coding-bb 0.0.3-beta.1 → 0.0.3-beta.13

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.
Files changed (100) hide show
  1. package/cbb/dev-standards/rules/cbb-ai-behavior.md +118 -116
  2. package/cbb/lib/install/claude-code.js +1 -3
  3. package/cbb/lib/install/codebuddy.js +30 -0
  4. package/cbb/lib/install/init.js +295 -119
  5. package/cbb/lib/install/opencode.js +35 -31
  6. package/cbb/lib/install/qoder.js +1 -3
  7. package/cbb/lib/install/rule-rewrite.js +27 -0
  8. package/cbb/lib/install/trae.js +33 -0
  9. package/cbb/lib/openspec/index.js +337 -554
  10. package/cbb/lib/superpowers/index.js +246 -265
  11. package/cbb/lib/utils/check-update.js +8 -1
  12. package/cbb/lib/utils/gitignore.js +2 -0
  13. package/cbb/lib/utils/settings.js +17 -4
  14. package/cbb/lib/utils/tar.js +92 -0
  15. package/cbb/lib/utils/upstream.js +90 -0
  16. package/cbb/worktrees/skills/cbb-worktree-close/SKILL.md +32 -23
  17. package/cbb/worktrees/skills/cbb-worktree-close/scripts/remove-worktrees.js +2 -2
  18. package/cbb/worktrees/skills/cbb-worktree-init/SKILL.md +36 -28
  19. package/cbb/worktrees/skills/cbb-worktree-init/scripts/create-worktrees.js +15 -37
  20. package/cbb/worktrees/skills/cbb-worktree-init/scripts/update-gitignore.js +25 -9
  21. package/cbb/worktrees/skills/cbb-worktree-push/SKILL.md +108 -31
  22. package/cbb/worktrees/skills/cbb-worktree-push/scripts/make-mr-urls.js +211 -0
  23. package/config/openspec/schemas/spec-driven/schema.yaml +38 -30
  24. package/config/openspec/schemas/spec-driven/templates/design.md +0 -18
  25. package/config/openspec/schemas/spec-driven/templates/proposal.md +4 -3
  26. package/config/upstream-mirrors.json +12 -0
  27. package/config/workspace-agents.sample.md +43 -39
  28. package/openspec/.version +2 -3
  29. package/openspec/commands/apply.md +189 -175
  30. package/openspec/commands/archive.md +237 -216
  31. package/openspec/commands/bulk-archive.md +355 -327
  32. package/openspec/commands/continue.md +116 -105
  33. package/openspec/commands/explore.md +230 -199
  34. package/openspec/commands/ff.md +115 -104
  35. package/openspec/commands/new.md +74 -63
  36. package/openspec/commands/onboard.md +557 -548
  37. package/openspec/commands/propose.md +161 -150
  38. package/openspec/commands/sync.md +277 -249
  39. package/openspec/commands/update.md +92 -80
  40. package/openspec/commands/verify.md +175 -162
  41. package/openspec/skills/openspec-apply-change/SKILL.md +20 -5
  42. package/openspec/skills/openspec-archive-change/SKILL.md +30 -8
  43. package/openspec/skills/openspec-bulk-archive-change/SKILL.md +36 -6
  44. package/openspec/skills/openspec-continue-change/SKILL.md +14 -2
  45. package/openspec/skills/openspec-explore/SKILL.md +21 -9
  46. package/openspec/skills/openspec-ff-change/SKILL.md +14 -2
  47. package/openspec/skills/openspec-new-change/SKILL.md +13 -1
  48. package/openspec/skills/openspec-onboard/SKILL.md +49 -39
  49. package/openspec/skills/openspec-propose/SKILL.md +15 -3
  50. package/openspec/skills/openspec-sync-specs/SKILL.md +31 -2
  51. package/openspec/skills/openspec-update-change/SKILL.md +27 -14
  52. package/openspec/skills/openspec-verify-change/SKILL.md +17 -3
  53. package/package.json +2 -2
  54. package/superpowers/.version +4 -4
  55. package/superpowers/skills/brainstorming/SKILL.md +47 -12
  56. package/superpowers/skills/brainstorming/scripts/frame-template.html +213 -213
  57. package/superpowers/skills/brainstorming/scripts/server.cjs +723 -723
  58. package/superpowers/skills/brainstorming/visual-companion.md +6 -6
  59. package/superpowers/skills/diagnosing-superpowers/SKILL.md +120 -0
  60. package/superpowers/skills/diagnosing-superpowers/prompts/analyst-common.md +38 -0
  61. package/superpowers/skills/diagnosing-superpowers/prompts/cost-and-time.md +28 -0
  62. package/superpowers/skills/diagnosing-superpowers/prompts/plan-adherence.md +29 -0
  63. package/superpowers/skills/diagnosing-superpowers/prompts/quality-evidence.md +26 -0
  64. package/superpowers/skills/diagnosing-superpowers/prompts/repeated-work.md +30 -0
  65. package/superpowers/skills/diagnosing-superpowers/prompts/request-conflicts.md +20 -0
  66. package/superpowers/skills/diagnosing-superpowers/prompts/scrub-audit.md +33 -0
  67. package/superpowers/skills/diagnosing-superpowers/prompts/scrub.md +29 -0
  68. package/superpowers/skills/diagnosing-superpowers/prompts/similar-session.md +38 -0
  69. package/superpowers/skills/diagnosing-superpowers/prompts/skill-timeline.md +30 -0
  70. package/superpowers/skills/diagnosing-superpowers/prompts/stumbles.md +28 -0
  71. package/superpowers/skills/diagnosing-superpowers/references/context-safety.md +22 -0
  72. package/superpowers/skills/diagnosing-superpowers/references/github-issues.md +47 -0
  73. package/superpowers/skills/diagnosing-superpowers/references/redaction-policy.md +34 -0
  74. package/superpowers/skills/diagnosing-superpowers/references/session-discovery.md +31 -0
  75. package/superpowers/skills/diagnosing-superpowers/templates/bundle-README.md +77 -0
  76. package/superpowers/skills/diagnosing-superpowers/templates/case.md +64 -0
  77. package/superpowers/skills/diagnosing-superpowers/templates/issue.md +51 -0
  78. package/superpowers/skills/diagnosing-superpowers/templates/report.md +82 -0
  79. package/superpowers/skills/executing-plans/SKILL.md +350 -41
  80. package/superpowers/skills/executing-plans/scripts/task-done +52 -0
  81. package/superpowers/skills/executing-plans/scripts/task-start +28 -0
  82. package/superpowers/skills/requesting-code-review/SKILL.md +1 -1
  83. package/superpowers/skills/requesting-code-review/code-reviewer.md +17 -0
  84. package/superpowers/skills/subagent-driven-development/SKILL.md +18 -18
  85. package/superpowers/skills/subagent-driven-development/re-review-prompt.md +1 -1
  86. package/superpowers/skills/subagent-driven-development/scripts/review-package +53 -46
  87. package/superpowers/skills/subagent-driven-development/scripts/sdd-workspace +82 -40
  88. package/superpowers/skills/subagent-driven-development/scripts/task-brief +43 -41
  89. package/superpowers/skills/subagent-driven-development/task-reviewer-prompt.md +2 -2
  90. package/superpowers/skills/systematic-debugging/root-cause-tracing.md +1 -1
  91. package/superpowers/skills/test-driven-development/SKILL.md +10 -0
  92. package/superpowers/skills/using-superpowers/SKILL.md +2 -0
  93. package/superpowers/skills/using-superpowers/references/claude-code-tools.md +29 -0
  94. package/superpowers/skills/using-superpowers/references/muse-tools.md +35 -0
  95. package/superpowers/skills/writing-plans/SKILL.md +30 -9
  96. package/superpowers/skills/writing-skills/SKILL.md +4 -2
  97. package/superpowers/skills/writing-skills/graphviz-conventions.dot +171 -171
  98. package/cbb/worktrees/commands/worktree-close.md +0 -64
  99. package/cbb/worktrees/commands/worktree-init.md +0 -51
  100. 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 "D:/workspace/<ws_root>/.qoder/skills/cbb-worktree-close/scripts/check-env.js"
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 "D:/workspace/<ws_root>/.qoder/skills/cbb-worktree-close/scripts/find-workspace-root.js"
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 "D:/workspace/<ws_root>/.qoder/skills/cbb-worktree-close/scripts/find-target-worktree.js "<ws_root>"
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 "D:/workspace/<ws_root>/.qoder/skills/cbb-worktree-close/scripts/discover-apps.js "<ws_root>" "<branch>"
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 配置目录(`.claude/`、`.qoder/`、`.cbb/`)在每个 worktree 内由 init-worktree 独立安装,随 worktree 目录删除而清理,skill 无需特殊处理。
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 "D:/workspace/<ws_root>/.qoder/skills/cbb-worktree-close/scripts/check-unarchived.js "<ws_root>"
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 "D:/workspace/<ws_root>/.qoder/skills/cbb-worktree-close/scripts/safety-check.js "<ws_root>" "<branch>"
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 "<skill>/scripts/delete-branches.js" "<ws_root>" "<branch>" local
239
+ node "<skill_dir>/scripts/delete-branches.js" "<ws_root>" "<branch>" local
230
240
 
231
241
  # 删本地 + 远程分支
232
- node "<skill>/scripts/delete-branches.js" "<ws_root>" "<branch>" local_remote
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`(或 `/cbb:worktree-push`),完成后**重新跑** Step 2 safety-check 确认 `unpushed_count = 0` 再回到本步骤
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 "D:/workspace/<ws_root>/.qoder/skills/cbb-worktree-close/scripts/remove-worktrees.js "<ws_root>" "<branch>" "<force>"
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 持有排他句柄时触发。元数据已清理保证分支删除不受影响;物理目录留给 init-worktree 下次创建时自愈(见 init Step 4.0)
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 "<skill>/scripts/delete-branches.js" "<ws_root>" "<branch>" local
346
+ node "<skill_dir>/scripts/delete-branches.js" "<ws_root>" "<branch>" local
337
347
  # 或
338
- node "<skill>/scripts/delete-branches.js" "<ws_root>" "<branch>" local_remote
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/`、`.cbb/` 等源目录)
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 配置目录在每个 worktree 内独立安装**:关闭 worktree 时这些目录随 worktree 目录一起删除
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 + ' 元数据已清理,物理目录被锁定(将在下次 init 时自动清理)');
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)后手动删除;或下次 init-worktree 时自动清理');
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
- **重要**:所有脚本必须用**绝对路径**调用。`./scripts/...` 相对路径在 Agent 运行环境中不适用(AI agent 的 cwd 不一定是技能目录)。
25
+ **重要**:本技能的所有脚本位于**本 SKILL.md 所在目录的 `scripts/` 下**(skill 目录)。
32
26
 
33
- 调用方式(推荐用绝对路径):
34
- ```
35
- node "D:\workspace\<用户工作空间>\.qoder\skills\cbb-worktree-init\scripts\<name>.js" <参数>
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 "D:/workspace/<ws_root>/.qoder/skills/cbb-worktree-init/scripts/check-env.js"
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 "D:/workspace/<ws_root>/.qoder/skills/cbb-worktree-init/scripts/check-env-deep.js"
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 "D:/workspace/<ws_root>/.qoder/skills/cbb-worktree-init/scripts/parse-config.js"
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 "D:/workspace/<ws_root>/.qoder/skills/cbb-worktree-init/scripts/sync-repos.js" "<ws_root>"
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 "D:/workspace/<ws_root>/.qoder/skills/cbb-worktree-init/scripts/check-repos.js" "<ws_root>"
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 "D:/workspace/<ws_root>/.qoder/skills/cbb-worktree-init/scripts/update-gitignore.js "<ws_root>" "<branch>"
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
- > ⚠️ 必须在提交后切回主分支,否则 Step 3.4 `git worktree add` 会因分支已检出而失败(脚本已自动处理)。
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 "D:/workspace/<ws_root>/.qoder/skills/cbb-worktree-init/scripts/create-branches.js" "<ws_root>" "<branch>"
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 "D:/workspace/<ws_root>/.qoder/skills/cbb-worktree-init/scripts/push-branches.js" "<ws_root>" "<branch>"
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 "D:/workspace/<ws_root>/.qoder/skills/cbb-worktree-init/scripts/create-worktrees.js" "<ws_root>" "<branch>"
291
+ node "<skill_dir>/scripts/create-worktrees.js" "<ws_root>" "<branch>"
291
292
  ```
292
293
 
293
294
  脚本内部自动完成:
294
- 1. **自愈清理**:`git worktree prune` + 物理目录清理(close IDE 锁未能删除的物理目录)
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 **不会**自动删除;如需清理用 `/cbb:worktree-close` 关闭后重建 |
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;开发过程中产生的提交必须调 `/cbb:worktree-push` 或 `cbb-worktree-push` skill 推送(详见该 skill) |
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 "<WS_ROOT>\.qoder\skills\cbb-worktree-init\scripts\<name>.js" "<绝对路径>" "<branch>"` 形式发起(apps 由脚本内部从 workspace-config.json 读取),**不依赖跨调用的 cwd**
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(含自愈清理) | ws_root, branch | 0/1/2/3 |
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. 自愈清理:prune + 物理目录清理
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);