@jspg-ai/coding-bb 0.0.1

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 (183) hide show
  1. package/README.md +41 -0
  2. package/cbb/dev-standards/rules/cbb-ai-behavior.md +104 -0
  3. package/cbb/dev-standards/rules/cbb-coding-rule.md +50 -0
  4. package/cbb/dev-standards/rules/cbb-priority.md +56 -0
  5. package/cbb/dev-standards/skills/architecture-specs/SKILL.md +129 -0
  6. package/cbb/dev-standards/skills/coding-specs/SKILL.md +376 -0
  7. package/cbb/dev-standards/skills/coding-specs/references/concurrency.md +53 -0
  8. package/cbb/dev-standards/skills/coding-specs/references/config-center.md +16 -0
  9. package/cbb/dev-standards/skills/coding-specs/references/distributed.md +42 -0
  10. package/cbb/dev-standards/skills/coding-specs/references/es-coding.md +60 -0
  11. package/cbb/dev-standards/skills/coding-specs/references/scheduled-task.md +25 -0
  12. package/cbb/dev-standards/skills/coding-specs/references/security.md +11 -0
  13. package/cbb/dev-standards/skills/coding-specs/references/unit-testing.md +43 -0
  14. package/cbb/dev-standards/skills/es-design-specs/SKILL.md +104 -0
  15. package/cbb/dev-standards/skills/mysql-design-specs/SKILL.md +89 -0
  16. package/cbb/lib/install/claude-code.js +20 -0
  17. package/cbb/lib/install/cleanup.js +64 -0
  18. package/cbb/lib/install/init.js +1875 -0
  19. package/cbb/lib/install/qoder.js +20 -0
  20. package/cbb/lib/install/workspaces.js +232 -0
  21. package/cbb/lib/openspec/index.js +554 -0
  22. package/cbb/lib/superpowers/index.js +265 -0
  23. package/cbb/lib/utils/check-update.js +147 -0
  24. package/cbb/lib/utils/checkbox.js +383 -0
  25. package/cbb/lib/utils/gitignore.js +69 -0
  26. package/cbb/lib/utils/output.js +64 -0
  27. package/cbb/lib/utils/settings.js +119 -0
  28. package/cbb/lib/utils/version.js +135 -0
  29. package/cbb/lib/wiki/api-client.js +358 -0
  30. package/cbb/lib/wiki/cli.js +427 -0
  31. package/cbb/lib/wiki/convert.js +464 -0
  32. package/cbb/lib/wiki/index.js +218 -0
  33. package/cbb/lib/wiki/mermaid-guard.js +103 -0
  34. package/cbb/lib/wiki/split.js +131 -0
  35. package/cbb/tools/cbb-decompile-jar/SKILL.md +220 -0
  36. package/cbb/tools/cbb-decompile-jar/scripts/decompile.py +863 -0
  37. package/cbb/tools/cbb-design-to-wiki/SKILL.md +180 -0
  38. package/cbb/tools/cbb-mvn-guide/SKILL.md +281 -0
  39. package/cbb/tools/cbb-mvn-guide/scripts/mvn_jdk_manager.py +378 -0
  40. package/cbb/tools/cbb-mvn-guide/scripts/mvn_pom_jdk_reader.py +197 -0
  41. package/cbb/tools/cbb-plantuml-authoring/SKILL.md +448 -0
  42. package/cbb/tools/cbb-plantuml-authoring/references/drawing-templates.md +479 -0
  43. package/cbb/tools/cbb-wiki-ops/SKILL.md +134 -0
  44. package/cbb/tools/cbb-wiki-ops/scripts/check-auth.js +44 -0
  45. package/cbb/worktrees/_shared/scripts/find-target-worktree.js +96 -0
  46. package/cbb/worktrees/_shared/scripts/find-workspace-root.js +67 -0
  47. package/cbb/worktrees/_shared/scripts/push-core.js +136 -0
  48. package/cbb/worktrees/_shared/scripts/silence-popup.js +23 -0
  49. package/cbb/worktrees/commands/close.md +64 -0
  50. package/cbb/worktrees/commands/extend.md +65 -0
  51. package/cbb/worktrees/commands/init.md +55 -0
  52. package/cbb/worktrees/commands/push.md +42 -0
  53. package/cbb/worktrees/skills/openspec-close-worktree/SKILL.md +449 -0
  54. package/cbb/worktrees/skills/openspec-close-worktree/scripts/check-env.js +76 -0
  55. package/cbb/worktrees/skills/openspec-close-worktree/scripts/check-unarchived.js +48 -0
  56. package/cbb/worktrees/skills/openspec-close-worktree/scripts/delete-branches.js +136 -0
  57. package/cbb/worktrees/skills/openspec-close-worktree/scripts/discover-apps.js +76 -0
  58. package/cbb/worktrees/skills/openspec-close-worktree/scripts/find-target-worktree.js +96 -0
  59. package/cbb/worktrees/skills/openspec-close-worktree/scripts/find-workspace-root.js +67 -0
  60. package/cbb/worktrees/skills/openspec-close-worktree/scripts/remove-worktrees.js +124 -0
  61. package/cbb/worktrees/skills/openspec-close-worktree/scripts/safety-check.js +174 -0
  62. package/cbb/worktrees/skills/openspec-close-worktree/scripts/silence-popup.js +23 -0
  63. package/cbb/worktrees/skills/openspec-extend-worktree/SKILL.md +390 -0
  64. package/cbb/worktrees/skills/openspec-extend-worktree/scripts/check-env.js +95 -0
  65. package/cbb/worktrees/skills/openspec-extend-worktree/scripts/check-repos.js +98 -0
  66. package/cbb/worktrees/skills/openspec-extend-worktree/scripts/create-branches-and-worktrees.js +135 -0
  67. package/cbb/worktrees/skills/openspec-extend-worktree/scripts/find-target-worktree.js +96 -0
  68. package/cbb/worktrees/skills/openspec-extend-worktree/scripts/find-workspace-root.js +67 -0
  69. package/cbb/worktrees/skills/openspec-extend-worktree/scripts/install-ai.js +150 -0
  70. package/cbb/worktrees/skills/openspec-extend-worktree/scripts/list-available-apps.js +88 -0
  71. package/cbb/worktrees/skills/openspec-extend-worktree/scripts/push-core.js +136 -0
  72. package/cbb/worktrees/skills/openspec-extend-worktree/scripts/silence-popup.js +23 -0
  73. package/cbb/worktrees/skills/openspec-extend-worktree/scripts/sync-repos.js +93 -0
  74. package/cbb/worktrees/skills/openspec-init-worktree/SKILL.md +539 -0
  75. package/cbb/worktrees/skills/openspec-init-worktree/scripts/auto-open.js +136 -0
  76. package/cbb/worktrees/skills/openspec-init-worktree/scripts/check-env-deep.js +87 -0
  77. package/cbb/worktrees/skills/openspec-init-worktree/scripts/check-env.js +90 -0
  78. package/cbb/worktrees/skills/openspec-init-worktree/scripts/check-repos.js +171 -0
  79. package/cbb/worktrees/skills/openspec-init-worktree/scripts/create-branches.js +103 -0
  80. package/cbb/worktrees/skills/openspec-init-worktree/scripts/create-worktrees.js +154 -0
  81. package/cbb/worktrees/skills/openspec-init-worktree/scripts/find-workspace-root.js +67 -0
  82. package/cbb/worktrees/skills/openspec-init-worktree/scripts/generate-app-options.js +93 -0
  83. package/cbb/worktrees/skills/openspec-init-worktree/scripts/install-ai.js +177 -0
  84. package/cbb/worktrees/skills/openspec-init-worktree/scripts/parse-config.js +67 -0
  85. package/cbb/worktrees/skills/openspec-init-worktree/scripts/push-branches.js +101 -0
  86. package/cbb/worktrees/skills/openspec-init-worktree/scripts/push-core.js +136 -0
  87. package/cbb/worktrees/skills/openspec-init-worktree/scripts/silence-popup.js +23 -0
  88. package/cbb/worktrees/skills/openspec-init-worktree/scripts/sync-repos.js +95 -0
  89. package/cbb/worktrees/skills/openspec-init-worktree/scripts/update-gitignore.js +134 -0
  90. package/cbb/worktrees/skills/openspec-push-worktrees/SKILL.md +307 -0
  91. package/cbb/worktrees/skills/openspec-push-worktrees/scripts/commit-worktrees.js +269 -0
  92. package/cbb/worktrees/skills/openspec-push-worktrees/scripts/find-target-worktree.js +96 -0
  93. package/cbb/worktrees/skills/openspec-push-worktrees/scripts/find-workspace-root.js +67 -0
  94. package/cbb/worktrees/skills/openspec-push-worktrees/scripts/push-core.js +136 -0
  95. package/cbb/worktrees/skills/openspec-push-worktrees/scripts/push-worktrees.js +159 -0
  96. package/cbb/worktrees/skills/openspec-push-worktrees/scripts/silence-popup.js +23 -0
  97. package/config/cbb.yaml +6 -0
  98. package/config/config.sample.json +16 -0
  99. package/config/openspec/config.yaml +28 -0
  100. package/config/openspec/schemas/spec-driven/schema.yaml +247 -0
  101. package/config/openspec/schemas/spec-driven/templates/design.md +475 -0
  102. package/config/openspec/schemas/spec-driven/templates/proposal.md +58 -0
  103. package/config/openspec/schemas/spec-driven/templates/spec.md +8 -0
  104. package/config/openspec/schemas/spec-driven/templates/tasks.md +9 -0
  105. package/config/workspaces.json +15 -0
  106. package/openspec/.version +6 -0
  107. package/openspec/commands/apply.md +182 -0
  108. package/openspec/commands/archive.md +223 -0
  109. package/openspec/commands/bulk-archive.md +334 -0
  110. package/openspec/commands/continue.md +112 -0
  111. package/openspec/commands/explore.md +206 -0
  112. package/openspec/commands/ff.md +111 -0
  113. package/openspec/commands/new.md +70 -0
  114. package/openspec/commands/onboard.md +555 -0
  115. package/openspec/commands/propose.md +157 -0
  116. package/openspec/commands/sync.md +256 -0
  117. package/openspec/commands/update.md +85 -0
  118. package/openspec/commands/verify.md +169 -0
  119. package/openspec/skills/openspec-apply-change/SKILL.md +187 -0
  120. package/openspec/skills/openspec-archive-change/SKILL.md +181 -0
  121. package/openspec/skills/openspec-bulk-archive-change/SKILL.md +338 -0
  122. package/openspec/skills/openspec-continue-change/SKILL.md +117 -0
  123. package/openspec/skills/openspec-explore/SKILL.md +342 -0
  124. package/openspec/skills/openspec-ff-change/SKILL.md +116 -0
  125. package/openspec/skills/openspec-new-change/SKILL.md +76 -0
  126. package/openspec/skills/openspec-onboard/SKILL.md +560 -0
  127. package/openspec/skills/openspec-propose/SKILL.md +162 -0
  128. package/openspec/skills/openspec-sync-specs/SKILL.md +261 -0
  129. package/openspec/skills/openspec-update-change/SKILL.md +90 -0
  130. package/openspec/skills/openspec-verify-change/SKILL.md +174 -0
  131. package/package.json +46 -0
  132. package/superpowers/.version +6 -0
  133. package/superpowers/skills/brainstorming/SKILL.md +250 -0
  134. package/superpowers/skills/brainstorming/scripts/frame-template.html +213 -0
  135. package/superpowers/skills/brainstorming/scripts/helper.js +167 -0
  136. package/superpowers/skills/brainstorming/scripts/server.cjs +723 -0
  137. package/superpowers/skills/brainstorming/scripts/start-server.sh +209 -0
  138. package/superpowers/skills/brainstorming/scripts/stop-server.sh +120 -0
  139. package/superpowers/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
  140. package/superpowers/skills/brainstorming/visual-companion.md +299 -0
  141. package/superpowers/skills/dispatching-parallel-agents/SKILL.md +167 -0
  142. package/superpowers/skills/executing-plans/SKILL.md +64 -0
  143. package/superpowers/skills/finishing-a-development-branch/SKILL.md +225 -0
  144. package/superpowers/skills/receiving-code-review/SKILL.md +205 -0
  145. package/superpowers/skills/requesting-code-review/SKILL.md +95 -0
  146. package/superpowers/skills/requesting-code-review/code-reviewer.md +181 -0
  147. package/superpowers/skills/subagent-driven-development/SKILL.md +568 -0
  148. package/superpowers/skills/subagent-driven-development/implementer-prompt.md +154 -0
  149. package/superpowers/skills/subagent-driven-development/re-review-prompt.md +115 -0
  150. package/superpowers/skills/subagent-driven-development/scripts/review-package +46 -0
  151. package/superpowers/skills/subagent-driven-development/scripts/sdd-workspace +40 -0
  152. package/superpowers/skills/subagent-driven-development/scripts/task-brief +41 -0
  153. package/superpowers/skills/subagent-driven-development/task-reviewer-prompt.md +207 -0
  154. package/superpowers/skills/systematic-debugging/CREATION-LOG.md +119 -0
  155. package/superpowers/skills/systematic-debugging/SKILL.md +283 -0
  156. package/superpowers/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
  157. package/superpowers/skills/systematic-debugging/condition-based-waiting.md +115 -0
  158. package/superpowers/skills/systematic-debugging/defense-in-depth.md +122 -0
  159. package/superpowers/skills/systematic-debugging/find-polluter.sh +72 -0
  160. package/superpowers/skills/systematic-debugging/root-cause-tracing.md +169 -0
  161. package/superpowers/skills/systematic-debugging/test-academic.md +14 -0
  162. package/superpowers/skills/systematic-debugging/test-pressure-1.md +58 -0
  163. package/superpowers/skills/systematic-debugging/test-pressure-2.md +68 -0
  164. package/superpowers/skills/systematic-debugging/test-pressure-3.md +69 -0
  165. package/superpowers/skills/test-driven-development/SKILL.md +320 -0
  166. package/superpowers/skills/test-driven-development/writing-good-tests.md +198 -0
  167. package/superpowers/skills/using-git-worktrees/SKILL.md +167 -0
  168. package/superpowers/skills/using-superpowers/SKILL.md +63 -0
  169. package/superpowers/skills/using-superpowers/references/antigravity-tools.md +23 -0
  170. package/superpowers/skills/using-superpowers/references/codex-tools.md +108 -0
  171. package/superpowers/skills/using-superpowers/references/gemini-tools.md +63 -0
  172. package/superpowers/skills/using-superpowers/references/hermes-tools.md +56 -0
  173. package/superpowers/skills/using-superpowers/references/pi-tools.md +16 -0
  174. package/superpowers/skills/verification-before-completion/SKILL.md +120 -0
  175. package/superpowers/skills/writing-plans/SKILL.md +171 -0
  176. package/superpowers/skills/writing-plans/plan-document-reviewer-prompt.md +49 -0
  177. package/superpowers/skills/writing-skills/SKILL.md +679 -0
  178. package/superpowers/skills/writing-skills/anthropic-best-practices.md +1150 -0
  179. package/superpowers/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
  180. package/superpowers/skills/writing-skills/graphviz-conventions.dot +172 -0
  181. package/superpowers/skills/writing-skills/persuasion-principles.md +187 -0
  182. package/superpowers/skills/writing-skills/render-graphs.js +169 -0
  183. package/superpowers/skills/writing-skills/testing-skills-with-subagents.md +384 -0
@@ -0,0 +1,307 @@
1
+ ---
2
+ name: openspec-push-worktrees
3
+ description: "跨 worktree 提交并推送当前需求的所有关联仓库。触发:用户说'提交'/'commit'/'push'/'推送'/'提交并push'/'推到远端';用户在 .worktrees/worktree-<需求名>/ 下工作要求批量推送;apply 完成后准备推送所有 worktree;close-worktree 前需要先推送未提交改动。"
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: "1.0"
9
+ generatedBy: coding-bb
10
+ ---
11
+
12
+ # Push Worktrees(跨平台脚本化版本 v1.0)
13
+
14
+ 跨 worktree 提交并推送当前需求的所有关联仓库到 origin。专门解决"开发完成后批量推送"和"用户口语化提交并push"两类场景,弥补 [openspec-init-worktree Step 3.4](file:///d:/workspace/va-ai/coding-bb/cbb/worktrees/skills/openspec-init-worktree/SKILL.md) 仅推送初始空 commit 的缺口。
15
+
16
+ **启动时声明:** "我正在使用 openspec-push-worktrees 技能来批量提交并推送当前需求的所有 worktree 仓库。"
17
+
18
+ ## 核心原则
19
+
20
+ - 本技能的所有原子操作封装为 `scripts/*.js` Node.js 脚本,**跨平台执行**
21
+ - **两阶段**:先 `commit-worktrees.js` 提交所有 dirty 仓库,再 `push-worktrees.js` 推送所有 worktree 仓库。两个脚本独立退出码,AI agent 区分阶段
22
+ - 复用 `openspec-push-worktrees/scripts/push-core.js` 的 `pushSingle` / `classifyError` —— 与 `openspec-init-worktree` 的 `push-branches.js` 行为完全一致,错误码契约统一
23
+ - `find-workspace-root.js` / `find-target-worktree.js` 为共享脚本:源仓库真源在 `cbb/worktrees/_shared/scripts/`,部署后位于本 skill 的 `scripts/` 下,与 `openspec-extend-worktree` / `openspec-close-worktree` 逻辑完全一致
24
+ - **本技能支持两层执行位置**(与 `openspec-close-worktree` 对称):
25
+ - 工作空间根目录(`WS_ROOT/`)
26
+ - worktree 内部(`WS_ROOT/.worktrees/worktree-<需求名>/` 或其子目录)
27
+ - 输入约定:**自由文本**(commit 信息、需求名等)直接在对话中提问并等待回复;**固定选项多选**(如 "应用统一信息 vs 分别信息")使用 `AskUserQuestion`
28
+
29
+ ---
30
+
31
+ ## 调用脚本的统一方式
32
+
33
+ **所有脚本必须用绝对路径调用**(Qoder 沙箱中 AI agent 的 cwd 不一定是技能目录)。
34
+
35
+ ```
36
+ node "<ws_root>/.qoder/skills/openspec-push-worktrees/scripts/<name>.js" <参数>
37
+ ```
38
+
39
+ 脚本输出:
40
+ - 人类可读报告(OK / FAIL / WARN)
41
+ - 机器可读摘要(末行以 `COMMIT_RESULT` / `PUSH_RESULT` 开头)
42
+ - AI agent 解析末行摘要决定下一步
43
+
44
+ ---
45
+
46
+ ## Step 0:定位工作空间
47
+
48
+ ```bash
49
+ node "<ws_root>/.qoder/skills/openspec-push-worktrees/scripts/find-workspace-root.js"
50
+ ```
51
+
52
+ **解读末行**:
53
+ - 退出码 0 + 末行(纯路径) → WS_ROOT
54
+ - 退出码 1 → 未找到工作空间根目录,**终止流程**
55
+
56
+ ---
57
+
58
+ ## Step 1:识别目标 worktree
59
+
60
+ ```bash
61
+ node "<ws_root>/.qoder/skills/openspec-push-worktrees/scripts/find-target-worktree.js" "<ws_root>"
62
+ ```
63
+
64
+ **解读末行**:
65
+ - 退出码 0 + 末行(需求名) → 唯一识别,自动用之
66
+ - 退出码 1 + 末行 `MULTIPLE_WORKTREEES` → 多个 worktree,AI agent 在对话中让用户输入序号或分支名
67
+ - 退出码 2 + 末行 `NO_WORKTREE` → 无 worktree,提示用户先用 `openspec-init-worktree`
68
+
69
+ ---
70
+
71
+ ## Step 2:扫描所有 worktree 仓库状态(dry-run)
72
+
73
+ ```bash
74
+ # 仅 commit 阶段先做 dry-run 预览
75
+ node "<ws_root>/.qoder/skills/openspec-push-worktrees/scripts/commit-worktrees.js" "<ws_root>" "<branch>" --message "preview" --dry-run
76
+ ```
77
+
78
+ **解读末行 `COMMIT_RESULT`**:每项仓库的状态
79
+ - `dry-run`:dirty 文件数 + 预览 commit message
80
+ - `skipped (clean)`:干净仓库,无需处理
81
+ - `skipped (has-ahead-commits)`:已有 ahead commit(只 push,不重复 commit)
82
+ - `skipped (skip-repo)`:用户指定跳过
83
+ - `failed`:错误
84
+
85
+ > ⓘ **dry-run 必跑**:让用户确认将要 commit 的内容,避免误纳 `.idea/` 等被 git 忽略的文件(虽然 `git add -u` 已天然排除,但 dry-run 仍是最后的兜底确认)。
86
+
87
+ ---
88
+
89
+ ## Step 3:收集 commit 信息(对话中提问)
90
+
91
+ AI agent 在执行真实 commit 前**必须**在对话中向用户确认 commit 信息。**禁止**自动生成。
92
+
93
+ **问询模板**(直接对话提问,不用 `AskUserQuestion`):
94
+
95
+ ```
96
+ 检测到以下 worktree 仓库有未提交改动:
97
+ - workspace: 5 个文件(CONFIG.json, openspec/changes/foo/...)
98
+ - ep-foo-app: 12 个文件
99
+ - ep-bar-app: 3 个文件
100
+
101
+ 请提供 commit 信息:
102
+ [1] 所有仓库用同一条信息(推荐简单场景)
103
+ [2] 每仓库分别提供(推荐多仓库改不同东西)
104
+
105
+ 回复示例(统一):
106
+ feat(<branch>): 实现客户运费逻辑
107
+
108
+ 回复示例(分别):
109
+ workspace: chore(<branch>): 同步 openspec 标准
110
+ ep-foo-app: feat(<branch>): 实现 saveClientExpectFreight
111
+ ep-bar-app: refactor(<branch>): 拆分 PlanService
112
+ ```
113
+
114
+ **自动建议**(仅作辅助):agent 可以从 `openspec/changes/<branch>/tasks.md` 已完成的 task 描述生成建议,但**必须**由用户确认或修改后才能传给脚本。
115
+
116
+ ---
117
+
118
+ ## Step 4:批量 commit
119
+
120
+ ```bash
121
+ # 统一信息
122
+ node "<ws_root>/.qoder/skills/openspec-push-worktrees/scripts/commit-worktrees.js" "<ws_root>" "<branch>" --message "feat(<branch>): 实现客户运费逻辑"
123
+
124
+ # 多仓库分别
125
+ node "..." "<ws_root>" "<branch>" --per-repo "ep-foo-app:feat(<branch>): 实现 saveClientExpectFreight;ep-bar-app:refactor(<branch>): 拆分 PlanService"
126
+
127
+ # 含未追踪文件
128
+ node "..." "<ws_root>" "<branch>" --message "..." --untracked
129
+
130
+ # wt_path 形式(用户在 worktree 内)
131
+ node "..." "<ws_root>/.worktrees/worktree-<branch>" --message "..."
132
+ ```
133
+
134
+ **选项**:
135
+ | 选项 | 说明 |
136
+ |------|------|
137
+ | `--message / -m` | 必填(除非 --per-repo 完全覆盖)。commit 信息 |
138
+ | `--per-repo "<name>:msg;<name>:msg"` | 覆盖特定仓库的 commit 信息 |
139
+ | `--untracked` | 同时 stage 未追踪文件(默认 `git add -u`,加此项时 `git add -A`) |
140
+ | `--allow-empty` | 允许空提交 |
141
+ | `--dry-run` | 仅预览,不实际 add/commit |
142
+ | `--skip-repo <name>` | 跳过指定仓库 |
143
+
144
+ **解读末行 `COMMIT_RESULT`**:
145
+ - 退出码 0 → 全部成功(包含全部 skipped)
146
+ - 退出码 2 → 部分 commit 失败,**阻塞流程**
147
+
148
+ **错误处理**:
149
+ - 某仓库 `commit-failed`(如 hook 失败、lint 失败)→ AI 报告失败详情,让用户修复后重试;**禁止**自动 `--no-verify` 绕过 hook
150
+ - 某仓库 `add-failed` → 检查文件权限 / 路径
151
+ - 某仓库 `no-message`(用了 --per-repo 但没覆盖该仓库) → 让用户补充
152
+
153
+ ---
154
+
155
+ ## Step 5:批量 push
156
+
157
+ ```bash
158
+ node "<ws_root>/.qoder/skills/openspec-push-worktrees/scripts/push-worktrees.js" "<ws_root>" "<branch>"
159
+
160
+ # 或 wt_path 形式
161
+ node "..." "<ws_root>/.worktrees/worktree-<branch>"
162
+ ```
163
+
164
+ **解读末行 `PUSH_RESULT`**:
165
+ - 退出码 0 + 末行 `PUSH_RESULT [...]` → 全部推送成功
166
+ - 退出码 2 + 末行 `PUSH_RESULT [...]`(含 `failed` 或 `remote-exists` 项) → **阻塞流程**
167
+
168
+ **错误处理**(与 init-worktree Step 3.4 一致):
169
+
170
+ | 错误码 | 处理 |
171
+ |--------|------|
172
+ | `ssh-auth-failed` / `https-auth-failed` / `network-unreachable` / `branch-protected` / `no-remote` / `non-fast-forward` / `unknown` | 输出失败明细,按 error 码分组汇总。AskUserQuestion 提供三个选项:1) 修复后重试(推荐) 2) 跳过失败仓库继续 3) 完全终止 |
173
+ | `remote-exists` | 先在纯文本中输出远程 commit 元信息 + 本地 HEAD hash(**二次确认**),再 AskUserQuestion 提供两个选项:1) 关联到 origin/&lt;branch&gt;(执行 set-upstream-to) 2) 终止流程 |
174
+
175
+ **禁止**:脚本**禁止**自动 `git branch --set-upstream-to=origin/<branch>`;AI 在询问关联前**必须**先输出 commit 元信息。
176
+
177
+ ---
178
+
179
+ ## Step 6:汇总报告
180
+
181
+ ```
182
+ === 批量 commit + push 完成 ===
183
+ 目标 worktree: <branch>
184
+ workspace:
185
+ commit: ✅ 5 个文件
186
+ push: ✅ origin/<branch>
187
+ ep-foo-app:
188
+ commit: ⏭️ skipped (clean)
189
+ push: ✅ origin/<branch>
190
+ ep-bar-app:
191
+ commit: ❌ 失败 (commit-failed: pre-commit hook failed)
192
+ push: ⏸️ 未执行
193
+ ```
194
+
195
+ **任一仓库失败** → 阻塞整个流程,AI 必须询问用户决策。
196
+
197
+ ---
198
+
199
+ ## 最终报告
200
+
201
+ ```
202
+ ✅ worktree 推送完成
203
+ 需求名:<branch>
204
+ 工作空间根:<ws_root>
205
+ Commit 摘要:
206
+ workspace: feat(<branch>): 实现 XX
207
+ ep-foo-app: skipped (clean)
208
+ ep-bar-app: skipped (clean)
209
+ Push 摘要:
210
+ workspace: ✅ origin/<branch>
211
+ ep-foo-app: ✅ origin/<branch>
212
+ ep-bar-app: ⚠️ 远程分支已存在(需用户决策 → 已关联)
213
+ AI 配置目录:worktree 内独立安装,无需处理
214
+ 后续:可继续开发 / 调 /opsx:verify 验证 / 调 /opsx:archive 归档 / 调 /opsx:close 关闭
215
+ ```
216
+
217
+ ---
218
+
219
+ ## 快速参考
220
+
221
+ | 场景 | 处理方式 |
222
+ |------|----------|
223
+ | 用户说"提交并push"且 cwd 在 worktree 内 | 自动用 cwd 反推分支,Step 2 扫描后进入 Step 3 问 commit 信息 |
224
+ | 用户说"提交并push"且 cwd 在 WS_ROOT | Step 0 定位后,多个 worktree 时让用户选 |
225
+ | commit 信息缺失 | 必须询问用户;禁止自动生成(可基于 apply task 上下文给建议) |
226
+ | 多仓库需不同 commit 信息 | 用 `--per-repo "ep-foo:m1;ep-bar:m2"`,必须由用户逐条提供 |
227
+ | 仓库干净 | commit-worktrees 自动 skipped,只 push 不 commit |
228
+ | 仓库已有 ahead commit | commit-worktrees 自动 skipped(不重复 commit),只 push |
229
+ | 未追踪文件 | 默认不纳入(避免误纳 .idea);用户明确要求时加 `--untracked` |
230
+ | 远程分支已存在 | push 退出码 2,AI 输出元信息后由用户决策(关联/终止) |
231
+ | push 失败(SSH/HTTPS/网络/保护) | push 退出码 2,AI 按错误码分类处理,阻塞并询问用户 |
232
+ | close-worktree 前调用 | 先调本 skill 把 ahead 推送完,避免 close 时丢提交 |
233
+ | --dry-run | 任何时候想预览将要 commit 的内容时使用,不实际修改任何东西 |
234
+
235
+ ---
236
+
237
+ ## 常见错误
238
+
239
+ ### 自动生成 commit 信息不让用户确认
240
+
241
+ - **问题:** agent 拍脑袋写 "feat: xxx",与用户实际意图不符
242
+ - **修复:** Step 3 必须**直接对话提问**让用户输入或确认
243
+
244
+ ### 误纳未追踪文件
245
+
246
+ - **问题:** 用户 `.idea/` / `node_modules/` 等被自动 `git add -A` 误纳
247
+ - **修复:** 默认 `git add -u`(仅 tracked modified/deleted);用户明确要求时加 `--untracked`;提交前 Step 2 dry-run 预览
248
+
249
+ ### close-worktree 前忘记先 push
250
+
251
+ - **问题:** 调 close-worktree 时,close-safety-check 检测到 ahead 提交 → 询问用户「先推送 / 接受丢失 / 中止」;用户没意识到要调本 skill
252
+ - **修复:** close-worktree/SKILL.md Step 3.4 决策点已提示「先推送」选项;本 skill 是该选项的执行入口
253
+
254
+ ### apply 完成后不调 push 直接 close
255
+
256
+ - **问题:** apply 实现完 → 用户关掉 IDE → 调 close-worktree → ahead 提交永久丢失
257
+ - **修复:** 强烈建议 apply 完成后立即调本 skill 推送;或 close-worktree Step 3.4 兜底
258
+
259
+ ### 多仓库用同一条 commit 信息
260
+
261
+ - **问题:** workspace 改了 CONFIG.json,ep-foo-app 改了 Java 代码,硬塞同一条 "feat: 实现 XX" 信息不准确
262
+ - **修复:** 用 `--per-repo` 模式,让用户逐仓库提供
263
+
264
+ ### push 失败时跳过失败仓库直接继续
265
+
266
+ - **问题:** push 部分失败但脚本退出码 0,AI 误以为成功
267
+ - **修复:** push-worktrees.js 退出码 2 时必须阻塞流程并询问用户;禁止脚本或 AI 自动跳过失败仓库
268
+
269
+ ### 远程分支已存在时自动 set-upstream
270
+
271
+ - **问题:** 看到 origin/&lt;branch&gt; 已存在就直接 `git branch --set-upstream-to`,可能关联到他人的同名分支
272
+ - **修复:** 脚本输出 metadata 后必须由用户决策;AI 在 AskUserQuestion 之前必须先输出 commit 元信息
273
+
274
+ ---
275
+
276
+ ## 红线规则
277
+
278
+ **绝不允许**
279
+
280
+ - 自动生成 commit 信息不让用户确认
281
+ - 默认 `git add -A` 误纳未追踪文件
282
+ - push 失败后跳过失败仓库直接继续
283
+ - 远程分支已存在时脚本自动 set-upstream-to
284
+ - AI 询问关联选项前不输出 commit 元信息
285
+ - 沙箱内 node 不可用时强行继续 → 改为 fail-fast 提示用户
286
+
287
+ **必须执行**
288
+
289
+ - 严格按 Step 0 → 1 → 2 → 3 → 4 → 5 → 6 顺序执行
290
+ - 所有脚本调用以 `node "..." "<绝对路径>"` 形式发起
291
+ - Step 2 dry-run 必跑,让用户看到将要 commit 的内容
292
+ - Step 3 必问用户 commit 信息(自由文本),禁止自动生成
293
+ - commit 信息涉及业务语义时(feature/refactor/fix 等)必须从 apply task 上下文生成建议并由用户确认
294
+ - commit-worktrees / push-worktrees 退出码 2 时必须阻塞流程
295
+ - 远程分支已存在时 AI 必须先输出 commit 元信息再询问
296
+
297
+ ---
298
+
299
+ ## 附录:脚本清单
300
+
301
+ | 脚本 | 功能 | 参数 | 退出码 |
302
+ |------|------|------|--------|
303
+ | `find-workspace-root.js` | 定位工作空间根目录(共享脚本,源仓库真源在 `_shared/scripts/`) | 无 | 0/1 |
304
+ | `find-target-worktree.js` | 目标需求名识别(共享脚本,源仓库真源在 `_shared/scripts/`) | ws_root | 0/1/2 |
305
+ | `commit-worktrees.js` | 批量 commit(默认 `-u`,加 `--untracked` 时 `-A`) | ws_root, branch, --message / --per-repo / --untracked / --allow-empty / --dry-run / --skip-repo | 0/1/2 |
306
+ | `push-worktrees.js` | 批量 push(复用 push-core 错误归类) | ws_root, branch(或 wt_path) | 0/1/2 |
307
+ | `push-core.js` | 推送核心(classifyError / pushSingle / collectRemoteMetadata,共享脚本,源仓库真源在 `_shared/scripts/`) | (共享模块) | - |
@@ -0,0 +1,269 @@
1
+ require('./silence-popup');
2
+ /**
3
+ * openspec-push-worktrees 批量 commit 所有 worktree 仓库
4
+ *
5
+ * 用法:
6
+ * node commit-worktrees.js <ws_root> <branch> --message "msg" [--untracked] [--per-repo "ep-foo:m1;ep-bar:m2"] [--allow-empty] [--dry-run] [--skip-repo name]
7
+ * node commit-worktrees.js <wt_path> --message "msg" [options]
8
+ *
9
+ * 行为:
10
+ * 1. 扫描 <ws_root>/.worktrees/worktree-<branch>/ 下所有 git 仓库
11
+ * 2. 对每个仓库:
12
+ * a. git status --porcelain 检测 dirty
13
+ * b. 若 clean 或已有 ahead commit → 跳过
14
+ * c. git add -u(默认)或 -A(含 untracked,加 --untracked 时)
15
+ * d. git commit -m "<message>"
16
+ * 3. 逐个报告状态,输出末行 COMMIT_RESULT
17
+ *
18
+ * 退出码:
19
+ * 0 - 全部成功(包含全部 skipped)
20
+ * 1 - 参数错误
21
+ * 2 - 部分 commit 失败(AI agent 必须阻塞流程)
22
+ *
23
+ * --dry-run 时不执行 add/commit,只输出将要 commit 的文件清单
24
+ */
25
+
26
+ const fs = require('fs');
27
+ const path = require('path');
28
+ const { execSync } = require('child_process');
29
+
30
+ function parseArgs(argv) {
31
+ // argv[0] = node, argv[1] = script
32
+ // 第一个位置参数(argv[2])如果是 --option 则报错
33
+ const args = argv.slice(2);
34
+ if (args.length === 0) {
35
+ return { error: '用法:node commit-worktrees.js <ws_root|wt_path> <branch?> [options]' };
36
+ }
37
+ // 第一个非选项参数
38
+ const first = args[0];
39
+ if (first.startsWith('--')) {
40
+ return { error: '第一个位置参数必须是 <ws_root> 或 <wt_path>' };
41
+ }
42
+ const opts = {
43
+ message: null,
44
+ perRepo: {}, // { 'ep-foo': 'msg1', 'ep-bar': 'msg2' }
45
+ untracked: false,
46
+ allowEmpty: false,
47
+ dryRun: false,
48
+ skipRepos: new Set(),
49
+ };
50
+ let positional = [first];
51
+ for (let i = 1; i < args.length; i++) {
52
+ const a = args[i];
53
+ if (a === '--message' || a === '-m') {
54
+ opts.message = args[++i];
55
+ } else if (a === '--per-repo') {
56
+ const raw = args[++i] || '';
57
+ // 解析 "ep-foo:msg1;ep-bar:msg2"
58
+ for (const part of raw.split(';')) {
59
+ const idx = part.indexOf(':');
60
+ if (idx > 0) {
61
+ opts.perRepo[part.substring(0, idx).trim()] = part.substring(idx + 1);
62
+ }
63
+ }
64
+ } else if (a === '--untracked') {
65
+ opts.untracked = true;
66
+ } else if (a === '--allow-empty') {
67
+ opts.allowEmpty = true;
68
+ } else if (a === '--dry-run') {
69
+ opts.dryRun = true;
70
+ } else if (a === '--skip-repo') {
71
+ const name = args[++i];
72
+ if (name) opts.skipRepos.add(name);
73
+ } else if (a.startsWith('--')) {
74
+ return { error: '未知选项:' + a };
75
+ } else {
76
+ positional.push(a);
77
+ }
78
+ }
79
+ // 解析定位参数
80
+ if (positional.length === 1) {
81
+ // wt_path 形式
82
+ const wtPath = path.resolve(positional[0]);
83
+ const m = wtPath.match(/^(.+)[\\\/]\.worktrees[\\\/]worktree-(.+)$/);
84
+ if (!m) {
85
+ return { error: 'argv[2] 必须是 <ws_root>/.worktrees/worktree-<branch> 形态,或同时传 ws_root + branch' };
86
+ }
87
+ return { wsRoot: m[1], branch: m[2], opts };
88
+ } else if (positional.length >= 2) {
89
+ return { wsRoot: path.resolve(positional[0]), branch: positional[1], opts };
90
+ }
91
+ return { error: '缺少 ws_root / wt_path 参数' };
92
+ }
93
+
94
+ function isGitRepo(p) {
95
+ return fs.existsSync(path.join(p, '.git'));
96
+ }
97
+
98
+ function scanWorktreeRepos(wsRoot, branch) {
99
+ const wtRoot = path.join(wsRoot, '.worktrees', 'worktree-' + branch);
100
+ const repos = [];
101
+ if (!fs.existsSync(wtRoot)) {
102
+ return { wtRoot, repos, error: 'worktree 目录不存在:' + wtRoot };
103
+ }
104
+ if (isGitRepo(wtRoot)) {
105
+ repos.push({ name: 'workspace', wt: wtRoot, repo: wsRoot });
106
+ }
107
+ const entries = fs.readdirSync(wtRoot, { withFileTypes: true });
108
+ for (const e of entries) {
109
+ if (!e.isDirectory() || e.name.startsWith('.')) continue;
110
+ const appWt = path.join(wtRoot, e.name);
111
+ if (!isGitRepo(appWt)) continue;
112
+ repos.push({ name: e.name, wt: appWt, repo: path.join(wsRoot, '.code', e.name) });
113
+ }
114
+ return { wtRoot, repos, error: null };
115
+ }
116
+
117
+ function run(cmd, cwd) {
118
+ try {
119
+ return { ok: true, out: execSync(cmd, { cwd, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] }).toString().trim() };
120
+ } catch (e) {
121
+ return { ok: false, err: (e.stderr ? e.stderr.toString() : e.message || String(e)).trim() };
122
+ }
123
+ }
124
+
125
+ function escapeMessageForShell(msg) {
126
+ // 简单包裹:替换反斜杠和双引号后用双引号包围
127
+ return '"' + String(msg).replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\r?\n/g, ' ') + '"';
128
+ }
129
+
130
+ /**
131
+ * commit 单个仓库
132
+ * @returns {{ name, status, files?, ahead?, error?, message? }}
133
+ */
134
+ function commitSingle(wt, name, message, opts) {
135
+ // 1. 检查是否 skip
136
+ if (opts.skipRepos.has(name)) {
137
+ return { name, status: 'skipped', reason: 'skip-repo' };
138
+ }
139
+
140
+ // 2. 检测 status
141
+ const status = run('git status --porcelain', wt);
142
+ if (!status.ok) {
143
+ return { name, status: 'failed', error: 'status-failed', message: status.err };
144
+ }
145
+ const dirtyFiles = status.out ? status.out.split(/\r?\n/).filter(Boolean) : [];
146
+ if (dirtyFiles.length === 0) {
147
+ return { name, status: 'skipped', reason: 'clean' };
148
+ }
149
+
150
+ // 3. 检测 ahead(已有 ahead commit 但 dirty?少见,但防止误覆盖)
151
+ const ahead = run('git log @{u}..HEAD --oneline 2>/dev/null', wt);
152
+ if (ahead.ok && ahead.out) {
153
+ return { name, status: 'skipped', reason: 'has-ahead-commits', ahead_count: ahead.out.split('\n').length };
154
+ }
155
+
156
+ // 4. 决定 message(per-repo 优先)
157
+ const finalMessage = opts.perRepo[name] || message;
158
+ if (!finalMessage) {
159
+ return { name, status: 'failed', error: 'no-message', message: '该仓库未指定 commit message(缺少 --per-repo ' + name + ':...)' };
160
+ }
161
+
162
+ if (opts.dryRun) {
163
+ return { name, status: 'dry-run', files: dirtyFiles, message: finalMessage };
164
+ }
165
+
166
+ // 5. add
167
+ const addCmd = opts.untracked ? 'git add -A' : 'git add -u';
168
+ const add = run(addCmd, wt);
169
+ if (!add.ok) {
170
+ return { name, status: 'failed', step: 'add', error: 'add-failed', message: add.err };
171
+ }
172
+
173
+ // 6. commit
174
+ const commitArgs = ['git commit'];
175
+ if (opts.allowEmpty) commitArgs.push('--allow-empty');
176
+ commitArgs.push('-m', escapeMessageForShell(finalMessage));
177
+ const commit = run(commitArgs.join(' '), wt);
178
+ if (!commit.ok) {
179
+ return { name, status: 'failed', step: 'commit', error: 'commit-failed', message: commit.err };
180
+ }
181
+
182
+ return { name, status: 'committed', files: dirtyFiles, message: finalMessage };
183
+ }
184
+
185
+ /**
186
+ * 编排
187
+ * @param {{ wsRoot, branch, opts }} params
188
+ */
189
+ function executeCommit(params) {
190
+ const { wtRoot, repos, error } = scanWorktreeRepos(params.wsRoot, params.branch);
191
+ if (error) {
192
+ return { wtRoot, results: [], error };
193
+ }
194
+ const results = [];
195
+ for (const r of repos) {
196
+ const res = commitSingle(r.wt, r.name, params.message, params.opts);
197
+ results.push(res);
198
+ }
199
+ return { wtRoot, results, error: null };
200
+ }
201
+
202
+ module.exports = { parseArgs, scanWorktreeRepos, commitSingle, executeCommit };
203
+
204
+ function main() {
205
+ const parsed = parseArgs(process.argv);
206
+ if (parsed.error) {
207
+ console.error('❌ ' + parsed.error);
208
+ process.exit(1);
209
+ }
210
+
211
+ if (!parsed.opts.dryRun && !parsed.opts.message && Object.keys(parsed.opts.perRepo).length === 0) {
212
+ console.error('❌ 必须传 --message 或 --per-repo(除非 --dry-run)');
213
+ process.exit(1);
214
+ }
215
+
216
+ console.log('=== 批量 commit 所有 worktree 仓库 ===');
217
+ console.log('工作空间根:' + parsed.wsRoot);
218
+ console.log('需求名(分支):' + parsed.branch);
219
+ console.log('默认 message:' + (parsed.opts.message || '(无)'));
220
+ if (Object.keys(parsed.opts.perRepo).length > 0) {
221
+ console.log('per-repo 覆盖:' + JSON.stringify(parsed.opts.perRepo));
222
+ }
223
+ console.log('未追踪文件:' + (parsed.opts.untracked ? '包含 (-A)' : '不包含 (-u)'));
224
+ console.log('dry-run:' + (parsed.opts.dryRun ? '是' : '否'));
225
+
226
+ const { wtRoot, results, error } = executeCommit({
227
+ wsRoot: parsed.wsRoot,
228
+ branch: parsed.branch,
229
+ message: parsed.opts.message,
230
+ opts: parsed.opts,
231
+ });
232
+
233
+ if (error) {
234
+ console.error('❌ ' + error);
235
+ process.exit(1);
236
+ }
237
+
238
+ if (results.length === 0) {
239
+ console.log('⚠️ 未在 worktree 下发现任何 git 仓库');
240
+ console.log('---');
241
+ console.log('COMMIT_RESULT', JSON.stringify([]));
242
+ process.exit(0);
243
+ }
244
+
245
+ // 人类可读报告
246
+ for (const r of results) {
247
+ if (r.status === 'committed') {
248
+ const fileCount = (r.files || []).length;
249
+ console.log('✅ ' + r.name + ': committed ' + fileCount + ' 个文件');
250
+ } else if (r.status === 'skipped') {
251
+ console.log('⏭️ ' + r.name + ': skipped (' + r.reason + (r.ahead_count ? ', ' + r.ahead_count + ' ahead' : '') + ')');
252
+ } else if (r.status === 'dry-run') {
253
+ const fileCount = (r.files || []).length;
254
+ console.log('🔍 ' + r.name + ': dry-run, ' + fileCount + ' 个文件待 commit');
255
+ } else {
256
+ console.error('❌ ' + r.name + ': ' + r.status + (r.step ? '[' + r.step + ']' : '') + ' - ' + (r.message || r.error));
257
+ }
258
+ }
259
+
260
+ const hasFailure = results.some(r => r.status === 'failed');
261
+
262
+ console.log('---');
263
+ console.log('COMMIT_RESULT', JSON.stringify(results));
264
+ process.exit(hasFailure ? 2 : 0);
265
+ }
266
+
267
+ if (require.main === module) {
268
+ main();
269
+ }
@@ -0,0 +1,96 @@
1
+ require('./silence-popup');
2
+ /**
3
+ * openspec-shared 目标需求名识别
4
+ *
5
+ * 唯一真源:cbb/worktrees/_shared/scripts/find-target-worktree.js
6
+ * 由 scripts/sync-shared.js 展开到各 skill 的 scripts/ 下(extend / close / push)。
7
+ *
8
+ * 用法:node find-target-worktree.js <ws_root>
9
+ *
10
+ * 优先级:
11
+ * 1. cwd 在 .worktrees/worktree-xxx/ 内 → 自动从路径反推
12
+ * 2. .worktrees/ 下只有一个 worktree → 自动选用
13
+ * 3. 多个 worktree → 按 mtime 倒序,第一个为推荐
14
+ *
15
+ * 输出:
16
+ * 末行输出需求名(用于 AI agent 解析)
17
+ * 多个时按 [1] xxx ⭐ / [2] yyy 列出
18
+ *
19
+ * 退出码:
20
+ * 0 - 唯一识别(自动或唯一)
21
+ * 1 - 多个 worktree,需用户输入
22
+ * 2 - 无 worktree
23
+ */
24
+
25
+ const { execSync } = require('child_process');
26
+ const fs = require('fs');
27
+ const path = require('path');
28
+
29
+ const wsRoot = process.argv[2];
30
+
31
+ if (!wsRoot) {
32
+ console.error('❌ 用法:node find-target-worktree.js <ws_root>');
33
+ process.exit(1);
34
+ }
35
+
36
+ // 优先级 1:cwd 反推
37
+ const cwd = process.cwd();
38
+ const cwdMatch = cwd.match(/\.worktrees[\\\/]worktree-([^\\\/]+)/);
39
+ if (cwdMatch) {
40
+ const branch = cwdMatch[1];
41
+ console.log('✅ 从 cwd 反推需求名:' + branch);
42
+ console.log('---');
43
+ console.log(branch);
44
+ process.exit(0);
45
+ }
46
+
47
+ const worktreesDir = path.join(wsRoot, '.worktrees');
48
+ if (!fs.existsSync(worktreesDir)) {
49
+ console.error('❌ .worktrees 目录不存在:' + worktreesDir);
50
+ console.error(' 请先执行 openspec-init-worktree 创建 worktree');
51
+ console.log('---');
52
+ console.log('NO_WORKTREE');
53
+ process.exit(2);
54
+ }
55
+
56
+ // 列出所有 worktree-xxx 目录
57
+ const entries = fs.readdirSync(worktreesDir, { withFileTypes: true })
58
+ .filter(e => e.isDirectory() && e.name.startsWith('worktree-'))
59
+ .map(e => ({
60
+ name: e.name.substring('worktree-'.length),
61
+ fullName: e.name,
62
+ path: path.join(worktreesDir, e.name),
63
+ mtime: fs.statSync(path.join(worktreesDir, e.name)).mtime.getTime(),
64
+ }));
65
+
66
+ if (entries.length === 0) {
67
+ console.error('❌ 未找到任何 worktree');
68
+ console.error(' 请先执行 openspec-init-worktree 创建 worktree');
69
+ console.log('---');
70
+ console.log('NO_WORKTREE');
71
+ process.exit(2);
72
+ }
73
+
74
+ // 优先级 2:唯一
75
+ if (entries.length === 1) {
76
+ console.log('✅ 自动选用唯一 worktree:' + entries[0].name);
77
+ console.log('---');
78
+ console.log(entries[0].name);
79
+ process.exit(0);
80
+ }
81
+
82
+ // 优先级 3:多个 → 按 mtime 倒序
83
+ entries.sort((a, b) => b.mtime - a.mtime);
84
+
85
+ console.log('⚠️ 检测到 ' + entries.length + ' 个 worktree(按最近修改排序):');
86
+ entries.forEach((e, i) => {
87
+ if (i === 0) {
88
+ console.log(' [' + (i + 1) + '] ' + e.name + ' ⭐ (推荐:最近修改)');
89
+ } else {
90
+ console.log(' [' + (i + 1) + '] ' + e.name);
91
+ }
92
+ });
93
+ console.log('请在对话中输入序号(1/2/3)或完整分支名:');
94
+ console.log('---');
95
+ console.log('MULTIPLE_WORKTREEES');
96
+ process.exit(1);