@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
@@ -6,13 +6,13 @@ require('./silence-popup');
6
6
  * 用法:node update-gitignore.js <ws_root> <branch>
7
7
  *
8
8
  * 行为:
9
- * 1. stash 当前未提交修改
10
- * 2. 切换到目标分支
11
- * 3. 检查 .worktrees/ 和 .codespace/ 是否在 .gitignore
12
- * 4. 添加缺失条目
13
- * 5. 提交 .gitignore
14
- * 6. 切回原分支
15
- * 7. pop stash(如有)
9
+ * 1. stash 当前未提交修改(仅 untracked 时不产生 stash)
10
+ * 2. 目标分支不存在则从 HEAD 创建(首跑时分支尚未由 create-branches.js 建立,
11
+ * 顺序无关自愈;create-branches.js 后续会报 reused)
12
+ * 3. 切换到目标分支
13
+ * 4. 检查 .worktrees/ 和 .codespace/ 是否在 .gitignore,缺失则添加并提交
14
+ * 5. 切回原分支
15
+ * 6. pop stash(如有)
16
16
  *
17
17
  * 幂等:目标分支已在某个 worktree 中检出(init-worktree 重跑场景,上次已处理过
18
18
  * .gitignore)时,跳过全部操作直接输出 GITIGNORE_OK——避免 checkout 与 worktree 检出冲突。
@@ -67,8 +67,12 @@ const status = run('git status --porcelain', wsRoot);
67
67
  if (status.ok && status.out) {
68
68
  const stash = run('git stash push -m "init-worktree-temp"', wsRoot);
69
69
  if (stash.ok) {
70
- stashed = true;
71
- console.log(' stash 未提交修改');
70
+ if (/No local changes to save/.test(stash.out)) {
71
+ console.log('ℹ️ 仅 untracked 文件,无需 stash');
72
+ } else {
73
+ stashed = true;
74
+ console.log('✅ 已 stash 未提交修改');
75
+ }
72
76
  } else {
73
77
  console.error('❌ git stash 失败:' + stash.err);
74
78
  process.exit(2);
@@ -77,6 +81,18 @@ if (status.ok && status.out) {
77
81
  console.log('ℹ️ 无未提交修改,跳过 stash');
78
82
  }
79
83
 
84
+ // 2.1 目标分支不存在则从 HEAD 创建(Step 3.2 先于 Step 3.3 执行时分支尚未建立)
85
+ const branchList = run('git branch --list "' + branch + '"', wsRoot);
86
+ if (branchList.ok && !branchList.out) {
87
+ const create = run('git branch "' + branch + '"', wsRoot);
88
+ if (!create.ok) {
89
+ console.error('❌ 创建分支 ' + branch + ' 失败:' + create.err);
90
+ if (stashed) run('git stash pop', wsRoot);
91
+ process.exit(2);
92
+ }
93
+ console.log('✅ 分支 ' + branch + ' 不存在,已从 HEAD 创建');
94
+ }
95
+
80
96
  // 3. 切换到目标分支
81
97
  const checkout = run('git checkout "' + branch + '"', wsRoot);
82
98
  if (!checkout.ok) {
@@ -1,24 +1,21 @@
1
1
  ---
2
2
  name: cbb-worktree-push
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
3
+ description: "提交并推送当前需求的所有关联仓库(跨 worktree 批量),推送后提案生成 MR(合并请求)预填链接。触发:用户说'提交'/'commit'/'push'/'推送'/'提交并push'/'推到远端'/'提MR'/'提合并请求'/'merge request';用户在 .worktrees/worktree-<需求名>/ 下工作要求批量推送;apply 完成后准备推送所有 worktree;close-worktree 前需要先推送未提交改动。注意:本技能会先识别场景——不在 cbb 工作空间时降级为普通单仓库提交推送。"
10
4
  ---
11
5
 
12
- # Push Worktrees(跨平台脚本化版本 v1.0
6
+ # Push Worktrees(跨平台脚本化版本 v1.1
13
7
 
14
- worktree 提交并推送当前需求的所有关联仓库到 origin。专门解决"开发完成后批量推送"和"用户口语化提交并push"两类场景,弥补 [cbb-worktree-init Step 3.4](file:///d:/workspace/va-ai/coding-bb/cbb/worktrees/skills/cbb-worktree-init/SKILL.md) 仅推送初始空 commit 的缺口。
8
+ 提交并推送当前需求的所有关联仓库(跨 worktree 批量)。专门解决"开发完成后批量推送"和"用户口语化提交并push"两类场景,弥补 [cbb-worktree-init Step 3.4](file:///d:/workspace/va-ai/coding-bb/cbb/worktrees/skills/cbb-worktree-init/SKILL.md) 仅推送初始空 commit 的缺口。
15
9
 
16
- **启动时声明:** "我正在使用 cbb-worktree-push 技能来批量提交并推送当前需求的所有 worktree 仓库。"
10
+ > **本技能描述含宽泛触发词("提交"/"push"),允许被误触发**。因此第一步永远是 **Step 0 场景识别**:在 cbb 工作空间内才走跨 worktree 批量流程;在普通 git 仓库降级为单仓库提交推送;非 git 目录则退出。
11
+
12
+ **启动时声明:** "我正在使用 cbb-worktree-push 技能来推送当前需求的关联仓库。"
17
13
 
18
14
  ## 核心原则
19
15
 
20
16
  - 本技能的所有原子操作封装为 `scripts/*.js` Node.js 脚本,**跨平台执行**
21
17
  - **两阶段**:先 `commit-worktrees.js` 提交所有 dirty 仓库,再 `push-worktrees.js` 推送所有 worktree 仓库。两个脚本独立退出码,AI agent 区分阶段
18
+ - **push 后 MR 提案(Step 7)**:`make-mr-urls.js` 生成预填合并请求链接(可自动开浏览器);只生成链接,提交/评审/合并全部由人在平台完成,不触碰任何凭据
22
19
  - 复用 `cbb-worktree-push/scripts/push-core.js` 的 `pushSingle` / `classifyError` —— 与 `cbb-worktree-init` 的 `push-branches.js` 行为完全一致,错误码契约统一
23
20
  - `find-workspace-root.js` / `find-target-worktree.js` 为共享脚本:源仓库真源在 `cbb/worktrees/_shared/scripts/`,部署后位于本 skill 的 `scripts/` 下,与 `cbb-worktree-init` / `cbb-worktree-close` 逻辑完全一致
24
21
  - **本技能支持两层执行位置**(与 `cbb-worktree-close` 对称):
@@ -30,12 +27,18 @@ metadata:
30
27
 
31
28
  ## 调用脚本的统一方式
32
29
 
33
- **所有脚本必须用绝对路径调用**(Agent 运行环境中 AI agent cwd 不一定是技能目录)。
30
+ **重要**:本技能的所有脚本位于**本 SKILL.md 所在目录的 `scripts/` 下**(skill 目录)。
31
+
32
+ 调用时用**本技能目录的绝对路径**拼接脚本名(AI 读取本 SKILL.md 时即已知该目录):
34
33
 
35
34
  ```
36
- node "<ws_root>/.qoder/skills/cbb-worktree-push/scripts/<name>.js" <参数>
35
+ node "<skill_dir>/scripts/<name>.js" <参数>
37
36
  ```
38
37
 
38
+ > - `<skill_dir>` = 本 SKILL.md 所在目录的绝对路径(AI 从读取路径即可推出)
39
+ > - **cwd 必须保持在工作空间根目录**:脚本按 cwd 定位 `workspace-config.json` 与 git 仓库
40
+ > - 不要用 `./scripts/...` 裸相对路径(AI 的 cwd 不是技能目录,会解析失败)
41
+
39
42
  脚本输出:
40
43
  - 人类可读报告(OK / FAIL / WARN)
41
44
  - 机器可读摘要(末行以 `COMMIT_RESULT` / `PUSH_RESULT` 开头)
@@ -43,22 +46,48 @@ node "<ws_root>/.qoder/skills/cbb-worktree-push/scripts/<name>.js" <参数>
43
46
 
44
47
  ---
45
48
 
46
- ## Step 0:定位工作空间
49
+ ## Step 0:场景识别(先判场景,再定策略)
50
+
51
+ **本技能允许被"提交"/"push"这类宽泛措辞误触发**。执行任何操作前,先判断当前处于哪种场景,按场景选择策略——**不要硬套 cbb 工作空间流程**。
52
+
53
+ 判断依据(任一命中即定场景):
54
+
55
+ | 场景 | 判据 | 策略 |
56
+ |------|------|------|
57
+ | **A. cbb 工作空间根** | cwd(或其 git 顶层)含 `workspace-config.json`;`find-workspace-root.js` 退出码 0 | 走 **Step 1 → 6** 的跨 worktree 批量流程 |
58
+ | **B. worktree 内** | cwd 命中 `<ws_root>/.worktrees/worktree-<需求名>/` | 用 cwd 反推 `<需求名>`,走 **Step 1 → 6** 批量流程 |
59
+ | **C. 普通 git 仓库** | 在 git 仓库内,但无 `workspace-config.json`(`find-workspace-root.js` 退出码 1) | **降级为单仓库提交推送**:不调 worktree 脚本,按常规 `git add -u` → 向用户询问 commit 信息 → `git commit` → `git push`(保留本 skill 的"必问 commit 信息"约束) |
60
+ | **D. 非 git 目录** | `git rev-parse --is-inside-work-tree` 失败 | 说明"当前目录不是 git 仓库",终止,不做任何操作 |
61
+
62
+ **判场景与定位(A/B 场景):**
47
63
 
48
64
  ```bash
49
- node "<ws_root>/.qoder/skills/cbb-worktree-push/scripts/find-workspace-root.js"
65
+ node "<skill_dir>/scripts/find-workspace-root.js"
50
66
  ```
51
67
 
52
68
  **解读末行**:
53
- - 退出码 0 + 末行(纯路径) → WS_ROOT
54
- - 退出码 1 → 未找到工作空间根目录,**终止流程**
69
+ - 退出码 0 + 末行(纯路径) → WS_ROOT(场景 A/B),继续 Step 1
70
+ - 退出码 1 → 无工作空间:**先判断场景 C 还是 D**(`git rev-parse --is-inside-work-tree`):
71
+ - 是 git 仓库 → 场景 C,按降级策略走(见下)
72
+ - 不是 → 场景 D,终止
73
+
74
+ **场景 C 降级流程**(普通仓库误触):
75
+
76
+ 1. `git status` 报告改动现状
77
+ 2. 直接对话询问 commit 信息(**禁止**自动生成)
78
+ 3. `git add -u`(默认不含未追踪文件;用户明确要求时才 `git add -A`)
79
+ 4. `git commit -m "<用户提供的信息>"`
80
+ 5. `git push`(无 upstream 时先询问用户是否 `-u`)
81
+ 6. 汇报结果;**不要**提及 worktree 批量流程
82
+
83
+ > 场景 C 是兜底:让"提交代码"在普通仓库里也能正常完成,而不是因找不到 `workspace-config.json` 报错。
55
84
 
56
85
  ---
57
86
 
58
- ## Step 1:识别目标 worktree
87
+ ## Step 1:识别目标 worktree(A/B 场景)
59
88
 
60
89
  ```bash
61
- node "<ws_root>/.qoder/skills/cbb-worktree-push/scripts/find-target-worktree.js" "<ws_root>"
90
+ node "<skill_dir>/scripts/find-target-worktree.js" "<ws_root>"
62
91
  ```
63
92
 
64
93
  **解读末行**:
@@ -72,7 +101,7 @@ node "<ws_root>/.qoder/skills/cbb-worktree-push/scripts/find-target-worktree.js"
72
101
 
73
102
  ```bash
74
103
  # 仅 commit 阶段先做 dry-run 预览
75
- node "<ws_root>/.qoder/skills/cbb-worktree-push/scripts/commit-worktrees.js" "<ws_root>" "<branch>" --message "preview" --dry-run
104
+ node "<skill_dir>/scripts/commit-worktrees.js" "<ws_root>" "<branch>" --message "preview" --dry-run
76
105
  ```
77
106
 
78
107
  **解读末行 `COMMIT_RESULT`**:每项仓库的状态
@@ -119,16 +148,16 @@ AI agent 在执行真实 commit 前**必须**在对话中向用户确认 commit
119
148
 
120
149
  ```bash
121
150
  # 统一信息
122
- node "<ws_root>/.qoder/skills/cbb-worktree-push/scripts/commit-worktrees.js" "<ws_root>" "<branch>" --message "feat(<branch>): 实现客户运费逻辑"
151
+ node "<skill_dir>/scripts/commit-worktrees.js" "<ws_root>" "<branch>" --message "feat(<branch>): 实现客户运费逻辑"
123
152
 
124
153
  # 多仓库分别
125
- node "..." "<ws_root>" "<branch>" --per-repo "ep-foo-app:feat(<branch>): 实现 saveClientExpectFreight;ep-bar-app:refactor(<branch>): 拆分 PlanService"
154
+ node "<skill_dir>/scripts/commit-worktrees.js" "<ws_root>" "<branch>" --per-repo "ep-foo-app:feat(<branch>): 实现 saveClientExpectFreight;ep-bar-app:refactor(<branch>): 拆分 PlanService"
126
155
 
127
156
  # 含未追踪文件
128
- node "..." "<ws_root>" "<branch>" --message "..." --untracked
157
+ node "<skill_dir>/scripts/commit-worktrees.js" "<ws_root>" "<branch>" --message "..." --untracked
129
158
 
130
159
  # wt_path 形式(用户在 worktree 内)
131
- node "..." "<ws_root>/.worktrees/worktree-<branch>" --message "..."
160
+ node "<skill_dir>/scripts/commit-worktrees.js" "<ws_root>/.worktrees/worktree-<branch>" --message "..."
132
161
  ```
133
162
 
134
163
  **选项**:
@@ -155,10 +184,10 @@ node "..." "<ws_root>/.worktrees/worktree-<branch>" --message "..."
155
184
  ## Step 5:批量 push
156
185
 
157
186
  ```bash
158
- node "<ws_root>/.qoder/skills/cbb-worktree-push/scripts/push-worktrees.js" "<ws_root>" "<branch>"
187
+ node "<skill_dir>/scripts/push-worktrees.js" "<ws_root>" "<branch>"
159
188
 
160
189
  # 或 wt_path 形式
161
- node "..." "<ws_root>/.worktrees/worktree-<branch>"
190
+ node "<skill_dir>/scripts/push-worktrees.js" "<ws_root>/.worktrees/worktree-<branch>"
162
191
  ```
163
192
 
164
193
  **解读末行 `PUSH_RESULT`**:
@@ -196,6 +225,39 @@ node "..." "<ws_root>/.worktrees/worktree-<branch>"
196
225
 
197
226
  ---
198
227
 
228
+ ## Step 7:MR 提案(push 完成后)
229
+
230
+ Step 5 的 `PUSH_RESULT` 中至少一个仓库为 `pushed` / `skipped`(feature 分支已在远端)时,用 `AskUserQuestion`(固定选项)提案:
231
+
232
+ ```
233
+ 推送完成。要提 MR(合并请求)吗?
234
+ [提 MR 到测试分支] 生成预填的 MR 创建链接(如 dev/test)
235
+ [提 MR 到正式分支] 生成预填的 MR 创建链接(如 main/master)
236
+ [暂不提 MR] 结束本步
237
+ ```
238
+
239
+ 用户选择提 MR 后:
240
+
241
+ 1. **确定目标分支(交互式,禁止 AI 代决)**:`git branch -r` 列出现场实际的主干候选(dev / test / develop / main / master / release-*),请用户指定一个;全仓库统一用该目标分支(不存在此分支的仓库由脚本自动跳过)
242
+ 2. **生成链接**:
243
+
244
+ ```bash
245
+ node "<skill_dir>/scripts/make-mr-urls.js" "<ws_root>" "<branch>" "<target>" --open
246
+ ```
247
+
248
+ **解读末行 `MR_RESULT`**(逐仓库):
249
+ - `ok` → 已生成 URL(`--open` 时已开浏览器;用户在平台页面确认标题/评审人后提交)
250
+ - `no-diff` → 与目标分支无差异,跳过
251
+ - `not-pushed` → feature 分支不在远端(提示先完成 push)
252
+ - `no-target` → 远端无该目标分支(回退重新指定候选)
253
+ - `manual` → origin 无法解析为已知平台,输出手动创建指引;可用 `--platform gitlab|github|gitee` 强制
254
+
255
+ > 平台识别:`github.com` → PR compare、`gitee.com` → compare、**其余默认 GitLab**(企业自建主流);误判用 `--platform` 覆盖。
256
+
257
+ 3. **提 MR 后提醒用户后续链路**:测试环境部署 → 用户验收 → 验收通过后 `/opsx:archive` → 正式分支合并走公司发布流程 → 最后 `cbb-worktree-close`
258
+
259
+ ---
260
+
199
261
  ## 最终报告
200
262
 
201
263
  ```
@@ -210,8 +272,7 @@ node "..." "<ws_root>/.worktrees/worktree-<branch>"
210
272
  workspace: ✅ origin/<branch>
211
273
  ep-foo-app: ✅ origin/<branch>
212
274
  ep-bar-app: ⚠️ 远程分支已存在(需用户决策 → 已关联)
213
- AI 配置目录:worktree 内独立安装,无需处理
214
- 后续:可继续开发 / 调 /opsx:verify 验证 / 调 /opsx:archive 归档 / 调 /cbb:worktree-close 关闭
275
+ 后续:Step 7 提 MR 到测试分支 → 部署验收 → 验收通过后 /opsx:archive → 正式发布(公司流程)→ cbb-worktree-close
215
276
  ```
216
277
 
217
278
  ---
@@ -220,8 +281,11 @@ node "..." "<ws_root>/.worktrees/worktree-<branch>"
220
281
 
221
282
  | 场景 | 处理方式 |
222
283
  |------|----------|
223
- | 用户说"提交并push"且 cwd 在 worktree 内 | 自动用 cwd 反推分支,Step 2 扫描后进入 Step 3 问 commit 信息 |
224
- | 用户说"提交并push"且 cwd 在 WS_ROOT | Step 0 定位后,多个 worktree 时让用户选 |
284
+ | **任何触发** | **先做 Step 0 场景识别**,再决定策略 |
285
+ | 场景 A:cwd 在 cbb 工作空间根 | find-workspace-root 成功 多个 worktree 时让用户选 |
286
+ | 场景 B:cwd 在 worktree 内 | 自动用 cwd 反推分支,扫描后进入询问 commit 信息 |
287
+ | 场景 C:cwd 在普通 git 仓库(无 workspace-config.json) | **降级为单仓库提交推送**,不调用 worktree 脚本 |
288
+ | 场景 D:cwd 非 git 目录 | 说明情况并终止,不做任何操作 |
225
289
  | commit 信息缺失 | 必须询问用户;禁止自动生成(可基于 apply task 上下文给建议) |
226
290
  | 多仓库需不同 commit 信息 | 用 `--per-repo "ep-foo:m1;ep-bar:m2"`,必须由用户逐条提供 |
227
291
  | 仓库干净 | commit-worktrees 自动 skipped,只 push 不 commit |
@@ -230,12 +294,20 @@ node "..." "<ws_root>/.worktrees/worktree-<branch>"
230
294
  | 远程分支已存在 | push 退出码 2,AI 输出元信息后由用户决策(关联/终止) |
231
295
  | push 失败(SSH/HTTPS/网络/保护) | push 退出码 2,AI 按错误码分类处理,阻塞并询问用户 |
232
296
  | close-worktree 前调用 | 先调本 skill 把 ahead 推送完,避免 close 时丢提交 |
297
+ | push 完成后 | Step 7 MR 提案:AskUserQuestion 固定选项 → make-mr-urls.js 生成预填链接 + 开浏览器;只生成链接,不代合并 |
298
+ | MR 目标分支 | 交互式:`git branch -r` 列主干候选由用户指定,全仓库统一;缺该分支的仓库自动跳过(no-target) |
299
+ | MR 平台误判 | 非 github.com/gitee.com 默认按 GitLab 拼 URL;有误判用 `--platform gitlab\|github\|gitee` 强制 |
233
300
  | --dry-run | 任何时候想预览将要 commit 的内容时使用,不实际修改任何东西 |
234
301
 
235
302
  ---
236
303
 
237
304
  ## 常见错误
238
305
 
306
+ ### 误触后硬套 worktree 流程
307
+
308
+ - **问题:** 用户在普通仓库说"提交代码",技能被 description 匹配触发,直接跑 `find-workspace-root.js` 失败并报错终止
309
+ - **修复:** Step 0 先判场景;场景 C(普通 git 仓库)降级为单仓库提交推送,不调用 worktree 脚本、不提及批量流程
310
+
239
311
  ### 自动生成 commit 信息不让用户确认
240
312
 
241
313
  - **问题:** agent 拍脑袋写 "feat: xxx",与用户实际意图不符
@@ -283,16 +355,20 @@ node "..." "<ws_root>/.worktrees/worktree-<branch>"
283
355
  - 远程分支已存在时脚本自动 set-upstream-to
284
356
  - AI 询问关联选项前不输出 commit 元信息
285
357
  - 运行环境 node 不可用时强行继续 → 改为 fail-fast 提示用户
358
+ - 代为合并 MR / 直接向测试、正式分支 push / 触碰平台凭据 → MR 只生成预填链接,提交与合并由人在平台完成
359
+ - MR 目标分支由 AI 猜测代决 → 必须列候选让用户指定
286
360
 
287
361
  **必须执行**
288
362
 
289
- - 严格按 Step 0 → 1 → 2 → 3 → 4 → 5 → 6 顺序执行
290
- - 所有脚本调用以 `node "..." "<绝对路径>"` 形式发起
363
+ - 严格按 Step 0 → 1 → 2 → 3 → 4 → 5 → 6 顺序执行(场景 C/D 除外,按 Step 0 降级策略)
364
+ - 所有脚本调用以 `node "<skill_dir>/scripts/<name>.js"` 形式发起(`<skill_dir>` = 本 SKILL.md 所在目录)
365
+ - 执行脚本时 cwd 保持在工作空间根;场景 C 降级时按普通 git 流程
291
366
  - Step 2 dry-run 必跑,让用户看到将要 commit 的内容
292
367
  - Step 3 必问用户 commit 信息(自由文本),禁止自动生成
293
368
  - commit 信息涉及业务语义时(feature/refactor/fix 等)必须从 apply task 上下文生成建议并由用户确认
294
369
  - commit-worktrees / push-worktrees 退出码 2 时必须阻塞流程
295
370
  - 远程分支已存在时 AI 必须先输出 commit 元信息再询问
371
+ - push 成功后必须走 Step 7 MR 提案(AskUserQuestion 固定选项;用户选"暂不提"则正常结束)
296
372
 
297
373
  ---
298
374
 
@@ -304,4 +380,5 @@ node "..." "<ws_root>/.worktrees/worktree-<branch>"
304
380
  | `find-target-worktree.js` | 目标需求名识别(共享脚本,源仓库真源在 `_shared/scripts/`) | ws_root | 0/1/2 |
305
381
  | `commit-worktrees.js` | 批量 commit(默认 `-u`,加 `--untracked` 时 `-A`) | ws_root, branch, --message / --per-repo / --untracked / --allow-empty / --dry-run / --skip-repo | 0/1/2 |
306
382
  | `push-worktrees.js` | 批量 push(复用 push-core 错误归类) | ws_root, branch(或 wt_path) | 0/1/2 |
383
+ | `make-mr-urls.js` | 生成预填 MR/PR 创建链接(平台识别 + 远端校验 + diff 判定 + `--open` 开浏览器) | ws_root, branch, target(或 wt_path, target), --platform, --open | 0/1/2 |
307
384
  | `push-core.js` | 推送核心(classifyError / pushSingle / collectRemoteMetadata,共享脚本,源仓库真源在 `_shared/scripts/`) | (共享模块) | - |
@@ -0,0 +1,211 @@
1
+ #!/usr/bin/env node
2
+ require('./silence-popup');
3
+ /**
4
+ * cbb-worktree-push MR 链接生成(预填 URL + 可选开浏览器)
5
+ *
6
+ * 用法:
7
+ * node make-mr-urls.js <ws_root> <branch> <target> [--platform gitlab|github|gitee] [--open]
8
+ * node make-mr-urls.js <wt_path> <target> [...] # wt_path 形态同 push-worktrees
9
+ *
10
+ * 行为(对 workspace worktree + 各 app worktree 逐仓库):
11
+ * 1. git ls-remote 校验 feature 分支已在远端(push 过)、目标分支存在
12
+ * 2. git rev-list 判断与目标分支有无差异(无差异 → no-diff 跳过)
13
+ * 3. 解析 origin URL 判定平台(github.com / gitee.com,其余默认 GitLab;--platform 强制)
14
+ * 4. 生成预填 MR/PR 创建 URL;--open 时逐个开浏览器
15
+ *
16
+ * 安全红线:只生成链接,绝不代为合并、不触碰任何凭据(提交动作由人在平台页面完成)
17
+ *
18
+ * 退出码:
19
+ * 0 - 正常(含全部跳过)
20
+ * 1 - 参数错误
21
+ * 2 - 致命(worktree 不存在 / 无仓库)
22
+ *
23
+ * 末行 MR_RESULT [{name,status,url|reason}]
24
+ * status: ok | no-diff | not-pushed | no-target | manual | no-remote
25
+ */
26
+
27
+ const { execSync, spawn } = require('child_process');
28
+ const path = require('path');
29
+ const { scanWorktreeRepos } = require('./push-worktrees');
30
+
31
+ function parseArgs(argv) {
32
+ const flags = {};
33
+ const pos = [];
34
+ for (let i = 0; i < argv.length; i++) {
35
+ const a = argv[i];
36
+ if (a === '--open') flags.open = true;
37
+ else if (a === '--platform') { flags.platform = argv[++i]; }
38
+ else if (a.startsWith('--platform=')) { flags.platform = a.split('=')[1]; }
39
+ else pos.push(a);
40
+ }
41
+ let inputs;
42
+ if (pos.length === 2) {
43
+ const wtPath = path.resolve(pos[0]);
44
+ const m = wtPath.match(/^(.+)[\\\/]\.worktrees[\\\/]worktree-(.+)$/);
45
+ if (!m) return { error: '两参数形式时 argv[1] 必须是 <ws_root>/.worktrees/worktree-<branch> 形态' };
46
+ inputs = { wsRoot: m[1], branch: m[2], target: pos[1] };
47
+ } else if (pos.length === 3) {
48
+ inputs = { wsRoot: path.resolve(pos[0]), branch: pos[1], target: pos[2] };
49
+ } else {
50
+ return { error: '用法:node make-mr-urls.js <ws_root> <branch> <target> [--platform X] [--open] | node make-mr-urls.js <wt_path> <target> [--open]' };
51
+ }
52
+ if (flags.platform && !['gitlab', 'github', 'gitee'].includes(flags.platform)) {
53
+ return { error: '--platform 仅支持 gitlab / github / gitee' };
54
+ }
55
+ return { ...inputs, ...flags };
56
+ }
57
+
58
+ /** 解析 git remote URL → { webBase }(浏览器可点的仓库主页,无 .git 后缀) */
59
+ function parseRemoteUrl(url) {
60
+ const u = (url || '').trim().replace(/\.git$/, '');
61
+ if (!u) return null;
62
+ // scp 式 ssh:git@host:group/proj
63
+ let m = u.match(/^[^@/\s]+@([^:]+):(.+)$/);
64
+ if (m) return { host: m[1], repoPath: m[2], webBase: 'https://' + m[1] + '/' + m[2] };
65
+ // 显式 ssh:// 或 http(s)://
66
+ m = u.match(/^(ssh|https?):\/\/(?:[^@/\s]+@)?([^/]+)\/(.+)$/);
67
+ if (m) {
68
+ const scheme = m[1] === 'ssh' ? 'https' : m[1];
69
+ return { host: m[2], repoPath: m[3], webBase: scheme + '://' + m[2] + '/' + m[3] };
70
+ }
71
+ return null;
72
+ }
73
+
74
+ function detectPlatform(remoteUrlStr, override) {
75
+ if (override) return override;
76
+ const parsed = parseRemoteUrl(remoteUrlStr);
77
+ if (!parsed) return null;
78
+ const host = parsed.host.toLowerCase();
79
+ if (host === 'github.com' || host.endsWith('.github.com')) return 'github';
80
+ if (host === 'gitee.com') return 'gitee';
81
+ // 企业自建以 GitLab 为绝对主流;误判时 --platform 强制覆盖
82
+ return 'gitlab';
83
+ }
84
+
85
+ /** 生成预填 MR/PR 创建 URL(branch/target 做 URL 编码) */
86
+ function buildMrUrl(platform, webBase, branch, target) {
87
+ const b = encodeURIComponent(branch);
88
+ const t = encodeURIComponent(target);
89
+ if (platform === 'gitlab') {
90
+ return webBase + '/-/merge_requests/new?merge_request%5Bsource_branch%5D=' + b + '&merge_request%5Btarget_branch%5D=' + t;
91
+ }
92
+ if (platform === 'github') {
93
+ return webBase + '/compare/' + t + '...' + b + '?expand=1';
94
+ }
95
+ if (platform === 'gitee') {
96
+ return webBase + '/compare/' + t + '...' + b;
97
+ }
98
+ return null;
99
+ }
100
+
101
+ function defaultRun(cmd, cwd) {
102
+ try {
103
+ return { ok: true, out: execSync(cmd, { cwd, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] }).trim() };
104
+ } catch (e) {
105
+ return { ok: false, err: (e.stderr || e.message || String(e)).trim() };
106
+ }
107
+ }
108
+
109
+ /**
110
+ * 单仓库判定:ls-remote 拿 source/target 的远端 sha → rev-list 数差异
111
+ * @returns {{status:'ok'|'no-diff'|'not-pushed'|'no-target'|'no-remote', remote?:string}}
112
+ */
113
+ function inspectRepo(repoDir, branch, target, execImpl) {
114
+ const run = execImpl || defaultRun;
115
+ const remote = run('git remote get-url origin', repoDir);
116
+ if (!remote.ok) return { status: 'no-remote' };
117
+ const ls = run('git ls-remote --heads origin "' + branch + '" "' + target + '"', repoDir);
118
+ if (!ls.ok) return { status: 'no-remote' };
119
+ const refs = {};
120
+ ls.out.split('\n').forEach(line => {
121
+ const m = line.match(/^([0-9a-f]+)\s+refs\/heads\/(.+)$/);
122
+ if (m) refs[m[2]] = m[1];
123
+ });
124
+ if (!refs[branch]) return { status: 'not-pushed', remote: remote.out };
125
+ if (!refs[target]) return { status: 'no-target', remote: remote.out };
126
+ const ahead = run('git rev-list --count "' + refs[target] + '..' + refs[branch] + '"', repoDir);
127
+ if (ahead.ok && ahead.out === '0') return { status: 'no-diff', remote: remote.out };
128
+ // rev-list 失败(目标 sha 本地不可达等)→ 不阻塞,照常给链接
129
+ return { status: 'ok', remote: remote.out };
130
+ }
131
+
132
+ function openBrowser(url, platform) {
133
+ const os = process.platform;
134
+ try {
135
+ if (os === 'win32') spawn('cmd', ['/c', 'start', '""', url], { stdio: 'ignore', detached: true }).unref();
136
+ else if (os === 'darwin') spawn('open', [url], { stdio: 'ignore', detached: true }).unref();
137
+ else spawn('xdg-open', [url], { stdio: 'ignore', detached: true }).unref();
138
+ } catch (e) { /* 打不开不阻塞:URL 清单已输出 */ }
139
+ }
140
+
141
+ /**
142
+ * 编排:扫描 worktree 仓库 + 逐仓库判定与生成
143
+ * @param {{wsRoot:string, branch:string, target:string, platform?:string, execImpl?:Function}} opts
144
+ */
145
+ function makeMrUrls(opts) {
146
+ const { wtRoot, repos, error } = scanWorktreeRepos(opts.wsRoot, opts.branch);
147
+ if (error) return { repos: [], results: [], error };
148
+ const results = [];
149
+ for (const r of repos) {
150
+ const info = inspectRepo(r.wt, opts.branch, opts.target, opts.execImpl);
151
+ const item = { name: r.name, status: info.status };
152
+ if (info.status === 'ok') {
153
+ const platform = detectPlatform(info.remote, opts.platform);
154
+ const web = parseRemoteUrl(info.remote);
155
+ const url = platform && web ? buildMrUrl(platform, web.webBase, opts.branch, opts.target) : null;
156
+ if (url) { item.url = url; item.platform = platform; }
157
+ else { item.status = 'manual'; item.reason = 'origin 无法解析为已知平台仓库地址'; }
158
+ } else {
159
+ item.reason = {
160
+ 'no-diff': '与目标分支无差异,无需 MR',
161
+ 'not-pushed': 'feature 分支未推送到远端(先完成 push)',
162
+ 'no-target': '远端不存在目标分支',
163
+ 'no-remote': '仓库无 origin 远端',
164
+ }[info.status];
165
+ }
166
+ results.push(item);
167
+ }
168
+ return { wtRoot, repos, results, error: null };
169
+ }
170
+
171
+ module.exports = { parseArgs, parseRemoteUrl, detectPlatform, buildMrUrl, inspectRepo, makeMrUrls };
172
+
173
+ function main() {
174
+ const args = parseArgs(process.argv.slice(2));
175
+ if (args.error) {
176
+ console.error('❌ ' + args.error);
177
+ process.exit(1);
178
+ }
179
+ const { wsRoot, branch, target } = args;
180
+ console.log('=== 生成 MR 预填链接 ===');
181
+ console.log('需求分支:' + branch + ' → 目标分支:' + target);
182
+
183
+ const { results, error } = makeMrUrls({ wsRoot, branch, target, platform: args.platform });
184
+ if (error) {
185
+ console.error('❌ ' + error);
186
+ process.exit(2);
187
+ }
188
+ if (results.length === 0) {
189
+ console.error('❌ worktree 下未发现任何 git 仓库');
190
+ process.exit(2);
191
+ }
192
+
193
+ let opened = 0;
194
+ for (const r of results) {
195
+ if (r.status === 'ok') {
196
+ console.log('✅ ' + r.name + ' [' + r.platform + ']: ' + r.url);
197
+ if (args.open) { openBrowser(r.url); opened++; }
198
+ } else {
199
+ console.log('⏭️ ' + r.name + ': ' + r.reason + '(' + r.status + ')');
200
+ }
201
+ }
202
+ if (args.open && opened > 0) console.log('🌐 已在浏览器打开 ' + opened + ' 个 MR 创建页(人工确认标题/评审人后提交)');
203
+
204
+ console.log('---');
205
+ console.log('MR_RESULT', JSON.stringify(results));
206
+ process.exit(0);
207
+ }
208
+
209
+ if (require.main === module) {
210
+ main();
211
+ }