@jspg-ai/coding-bb 0.0.3-beta.12 → 0.0.3-beta.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cbb/dev-standards/rules/cbb-ai-behavior.md +16 -6
- package/cbb/worktrees/skills/cbb-worktree-push/SKILL.md +44 -3
- package/cbb/worktrees/skills/cbb-worktree-push/scripts/make-mr-urls.js +211 -0
- package/config/openspec/schemas/spec-driven/schema.yaml +12 -1
- package/config/workspace-agents.sample.md +11 -7
- package/package.json +2 -2
|
@@ -101,18 +101,28 @@ trigger: always_on
|
|
|
101
101
|
|
|
102
102
|
## 8. 业务空间目录纪律
|
|
103
103
|
|
|
104
|
-
**仅当工作目录是业务空间根(存在 `workspace-config.json
|
|
104
|
+
**仅当工作目录是业务空间根(存在 `workspace-config.json`)时生效。空间根支持两种开发模式,接到需求先判定本次属于哪种,再动手。**
|
|
105
105
|
|
|
106
|
-
|
|
106
|
+
**模式一 · worktree 模式(默认;凡涉及应用代码改动必走此路):**
|
|
107
|
+
|
|
108
|
+
接到需求先建工作树(`cbb-worktree-init` skill,或斜杠命令 `/cbb-worktree-init`),之后:
|
|
107
109
|
|
|
108
110
|
- 一切产生写操作的命令(git commit / openspec / 构建 / 测试 / 文件修改)必须以 `.worktrees/worktree-<需求名>/`(或其应用子目录)为工作目录:显式 `cd` 或绝对路径
|
|
109
|
-
-
|
|
110
|
-
- 只读命令(git status / log、查看文件)在空间根执行无妨
|
|
111
|
+
- 不在空间根主分支提交应用代码、不在空间根创建本模式的 openspec 变更产物
|
|
111
112
|
- 会话开始时确认本次需求对应的工作树路径;存在多个未完成工作树时,先与用户确认目标,不要猜
|
|
112
113
|
|
|
113
|
-
|
|
114
|
+
**模式二 · 轻量模式(仅限空间根仓库自身内容:openspec 产物、docs、空间配置):**
|
|
115
|
+
|
|
116
|
+
需求完全不触碰任何应用代码文件时,可直接在空间根开发:变更产物落空间根 `openspec/`,走完整 opsx 流程后提交 main 分支,**提交后即 push——main 就是交付线,无 worktree、无 MR,不得悬空本地提交**。
|
|
117
|
+
|
|
118
|
+
**两模式共同红线(无例外):**
|
|
119
|
+
|
|
120
|
+
- `.codespace/` 是工具维护的基准代码,不是开发区——任何模式下都不许在其中改应用代码
|
|
121
|
+
- 只读命令(git status / log、查看文件)在空间根执行无妨
|
|
122
|
+
|
|
123
|
+
**会话与工作目录是两回事:** AI 会话窗口始终停在**空间根**(worktree 内不装 AI 配置,无工作流命令),`/opsx:*` 等命令仍在空间根会话发起;worktree 模式下**产生写操作的工作目录**指向需求工作树(显式 `cd` 或绝对路径)。不要把会话切到 worktree 内。
|
|
114
124
|
|
|
115
|
-
**检验标准:**
|
|
125
|
+
**检验标准:** worktree 模式的变更产物只出现在需求工作树内;空间根 main 的 `git status` 干净(除工具维护的配置文件外),且轻量模式产生的 main 提交都已 push(本地 main 不长期领先 origin/main)。
|
|
116
126
|
|
|
117
127
|
---
|
|
118
128
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: cbb-worktree-push
|
|
3
|
-
description: "提交并推送当前需求的所有关联仓库(跨 worktree
|
|
3
|
+
description: "提交并推送当前需求的所有关联仓库(跨 worktree 批量),推送后提案生成 MR(合并请求)预填链接。触发:用户说'提交'/'commit'/'push'/'推送'/'提交并push'/'推到远端'/'提MR'/'提合并请求'/'merge request';用户在 .worktrees/worktree-<需求名>/ 下工作要求批量推送;apply 完成后准备推送所有 worktree;close-worktree 前需要先推送未提交改动。注意:本技能会先识别场景——不在 cbb 工作空间时降级为普通单仓库提交推送。"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Push Worktrees(跨平台脚本化版本 v1.
|
|
6
|
+
# Push Worktrees(跨平台脚本化版本 v1.1)
|
|
7
7
|
|
|
8
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 的缺口。
|
|
9
9
|
|
|
@@ -15,6 +15,7 @@ description: "提交并推送当前需求的所有关联仓库(跨 worktree
|
|
|
15
15
|
|
|
16
16
|
- 本技能的所有原子操作封装为 `scripts/*.js` Node.js 脚本,**跨平台执行**
|
|
17
17
|
- **两阶段**:先 `commit-worktrees.js` 提交所有 dirty 仓库,再 `push-worktrees.js` 推送所有 worktree 仓库。两个脚本独立退出码,AI agent 区分阶段
|
|
18
|
+
- **push 后 MR 提案(Step 7)**:`make-mr-urls.js` 生成预填合并请求链接(可自动开浏览器);只生成链接,提交/评审/合并全部由人在平台完成,不触碰任何凭据
|
|
18
19
|
- 复用 `cbb-worktree-push/scripts/push-core.js` 的 `pushSingle` / `classifyError` —— 与 `cbb-worktree-init` 的 `push-branches.js` 行为完全一致,错误码契约统一
|
|
19
20
|
- `find-workspace-root.js` / `find-target-worktree.js` 为共享脚本:源仓库真源在 `cbb/worktrees/_shared/scripts/`,部署后位于本 skill 的 `scripts/` 下,与 `cbb-worktree-init` / `cbb-worktree-close` 逻辑完全一致
|
|
20
21
|
- **本技能支持两层执行位置**(与 `cbb-worktree-close` 对称):
|
|
@@ -224,6 +225,39 @@ node "<skill_dir>/scripts/push-worktrees.js" "<ws_root>/.worktrees/worktree-<bra
|
|
|
224
225
|
|
|
225
226
|
---
|
|
226
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
|
+
|
|
227
261
|
## 最终报告
|
|
228
262
|
|
|
229
263
|
```
|
|
@@ -238,7 +272,7 @@ node "<skill_dir>/scripts/push-worktrees.js" "<ws_root>/.worktrees/worktree-<bra
|
|
|
238
272
|
workspace: ✅ origin/<branch>
|
|
239
273
|
ep-foo-app: ✅ origin/<branch>
|
|
240
274
|
ep-bar-app: ⚠️ 远程分支已存在(需用户决策 → 已关联)
|
|
241
|
-
|
|
275
|
+
后续:Step 7 提 MR 到测试分支 → 部署验收 → 验收通过后 /opsx:archive → 正式发布(公司流程)→ cbb-worktree-close
|
|
242
276
|
```
|
|
243
277
|
|
|
244
278
|
---
|
|
@@ -260,6 +294,9 @@ node "<skill_dir>/scripts/push-worktrees.js" "<ws_root>/.worktrees/worktree-<bra
|
|
|
260
294
|
| 远程分支已存在 | push 退出码 2,AI 输出元信息后由用户决策(关联/终止) |
|
|
261
295
|
| push 失败(SSH/HTTPS/网络/保护) | push 退出码 2,AI 按错误码分类处理,阻塞并询问用户 |
|
|
262
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` 强制 |
|
|
263
300
|
| --dry-run | 任何时候想预览将要 commit 的内容时使用,不实际修改任何东西 |
|
|
264
301
|
|
|
265
302
|
---
|
|
@@ -318,6 +355,8 @@ node "<skill_dir>/scripts/push-worktrees.js" "<ws_root>/.worktrees/worktree-<bra
|
|
|
318
355
|
- 远程分支已存在时脚本自动 set-upstream-to
|
|
319
356
|
- AI 询问关联选项前不输出 commit 元信息
|
|
320
357
|
- 运行环境 node 不可用时强行继续 → 改为 fail-fast 提示用户
|
|
358
|
+
- 代为合并 MR / 直接向测试、正式分支 push / 触碰平台凭据 → MR 只生成预填链接,提交与合并由人在平台完成
|
|
359
|
+
- MR 目标分支由 AI 猜测代决 → 必须列候选让用户指定
|
|
321
360
|
|
|
322
361
|
**必须执行**
|
|
323
362
|
|
|
@@ -329,6 +368,7 @@ node "<skill_dir>/scripts/push-worktrees.js" "<ws_root>/.worktrees/worktree-<bra
|
|
|
329
368
|
- commit 信息涉及业务语义时(feature/refactor/fix 等)必须从 apply task 上下文生成建议并由用户确认
|
|
330
369
|
- commit-worktrees / push-worktrees 退出码 2 时必须阻塞流程
|
|
331
370
|
- 远程分支已存在时 AI 必须先输出 commit 元信息再询问
|
|
371
|
+
- push 成功后必须走 Step 7 MR 提案(AskUserQuestion 固定选项;用户选"暂不提"则正常结束)
|
|
332
372
|
|
|
333
373
|
---
|
|
334
374
|
|
|
@@ -340,4 +380,5 @@ node "<skill_dir>/scripts/push-worktrees.js" "<ws_root>/.worktrees/worktree-<bra
|
|
|
340
380
|
| `find-target-worktree.js` | 目标需求名识别(共享脚本,源仓库真源在 `_shared/scripts/`) | ws_root | 0/1/2 |
|
|
341
381
|
| `commit-worktrees.js` | 批量 commit(默认 `-u`,加 `--untracked` 时 `-A`) | ws_root, branch, --message / --per-repo / --untracked / --allow-empty / --dry-run / --skip-repo | 0/1/2 |
|
|
342
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 |
|
|
343
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
|
+
}
|
|
@@ -227,7 +227,7 @@ apply:
|
|
|
227
227
|
**收尾(全部任务翻成 `- [x]` 后执行;本段为项目级硬约束,覆盖官方 apply 模板自带的完成提示语):**
|
|
228
228
|
|
|
229
229
|
1. **未决项自检**(逐项确认,任何一项未过都不得宣布完成):
|
|
230
|
-
- change 产物(proposal.md / design.md / specs/** / tasks.md / plan.md
|
|
230
|
+
- change 产物(proposal.md / design.md / specs/** / tasks.md / plan.md)已提交到需求分支(轻量模式:空间根 main 分支);未提交时先询问用户是否现在提交(可随 cbb-worktree-push 一并推送;轻量模式提交后即 push)
|
|
231
231
|
- 本会话产生的临时文件与环境改动(备份目录、未回写远端的本地提交、cherry-pick 等)已列清并交用户决策
|
|
232
232
|
2. **输出顺序纪律**:存在未决项时,本轮只列未决项并等待用户决策;**禁止同轮输出"下一步建议"**,禁止出现"规划与实施均已完成""可运行 archive"之类表述(产物未提交时归档会丢规划产物)
|
|
233
233
|
3. **全部未决项关闭后,唯一可建议的下一步是 `/opsx:verify`**:本项目工作流为 propose → apply → verify → archive,verify(强制 code-review + 产出 cr.md)是 archive 的前置阶段;**禁止直接建议 archive**,官方模板的 "You can archive this change" 提示语以本段为准
|
|
@@ -244,3 +244,14 @@ verify:
|
|
|
244
244
|
即便 AI 自认能 review,仍必须调 skill。
|
|
245
245
|
2. **MANDATORY 产出 `cr.md`**:把完整 Verification Report 写到 `openspec/changes/<name>/cr.md`,
|
|
246
246
|
缺则视为 verify 未完成。报告必须包含五段:Summary / CRITICAL / WARNING / SUGGESTION / Code Review。
|
|
247
|
+
|
|
248
|
+
**收尾(cr.md 产出且无 CRITICAL 遗留后执行;本段为项目级硬约束,覆盖官方 verify 模板自带的完成提示语):**
|
|
249
|
+
|
|
250
|
+
verify 通过后,用 AskUserQuestion(固定选项)向用户提供两条路径并等待选择,禁止替用户决定、禁止单一路径直接建议归档:
|
|
251
|
+
|
|
252
|
+
- **路径 A · 先验收后归档(推荐)**:执行 `cbb-worktree-push` 推送 feature 分支并提 MR 到测试分支 → 测试环境部署、用户验收 → 验收要改则在 worktree 内修复后再次 push(小改动无需重跑 verify)→ 验收通过后才 `/opsx:archive`
|
|
253
|
+
- **路径 B · 先归档后推送**:立即 `/opsx:archive` 落定变更产物(归档移动与 spec 同步发生在 feature 分支上)→ 再执行 `cbb-worktree-push` 一并推送 → 提 MR 到测试分支验收。注意:验收若发现问题,产物已归档,修复需走新 change 或将归档移回后继续;适合改动小、验收把握大的场景
|
|
254
|
+
|
|
255
|
+
用户选定后按该路径执行;官方模板的单一 "suggest archive" 提示语以本段为准。
|
|
256
|
+
|
|
257
|
+
**轻量模式变体**(本次 change 完全不涉及应用代码,产物在空间根仓库):无 worktree push / 测试分支 MR 环节,两条路径替换为——A(推荐)"先 push main 交付、观察无回归后再 archive" / B"立即 archive 落定产物,再 push main";main 提交不得悬空本地。
|
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
|
|
6
6
|
## 核心约定
|
|
7
7
|
|
|
8
|
-
1.
|
|
8
|
+
1. **双模式开发**:涉及**应用代码**的需求在 `.worktrees/worktree-<需求名>/` 隔离目录内进行(worktree 模式,默认);完全不触碰应用代码的需求(openspec 产物、docs、空间配置)可直接在空间根开发(轻量模式,提交 main 后即 push)。**AI 会话始终停在空间根**(worktree 内不装 AI 配置)。
|
|
9
9
|
2. **AI 配置安装在空间根**:编码规范、OpenSpec 命令、worktree 管理技能都装在本目录(`cbb setup` / `cbb update` 安装);worktree 内**不安装** AI 配置,AI 会话始终以空间根为基础。
|
|
10
|
-
3.
|
|
11
|
-
4.
|
|
10
|
+
3. **涉及应用代码的新需求先建 worktree**:使用 `/cbb-worktree-init <需求名>`(skill 形态,各工具写法见文末),或直接自然语言说"为 <需求名> 创建工作空间"。它会为 `workspace-config.json` 中的全量关联应用同步创建同名 worktree。
|
|
11
|
+
4. **开发命令在本会话执行,写操作按模式定落点**:需求提案 / 实现 / 验证 / 归档(`/opsx:propose` → `/opsx:apply` → `/opsx:verify` → `/opsx:archive`)在空间根会话执行;worktree 模式下所有产生写操作的命令(git commit / openspec / 构建 / 测试 / 文件修改)必须以 `.worktrees/worktree-<需求名>/`(或其应用子目录)为工作目录(显式 `cd` 或绝对路径),**不得往主分支提交应用代码**;轻量模式在空间根直接写、提交 main 后即 push。
|
|
12
12
|
5. **关联应用增减只改配置**:编辑 `workspace-config.json` 的 `apps` 数组(`name` / `repo` / `side` / `desc`),然后用同一需求名重跑 worktree 初始化即幂等补齐;不要手工 `git clone` 应用仓库。
|
|
13
|
-
6. **不要绕过 worktree 直接修改 `.codespace
|
|
13
|
+
6. **不要绕过 worktree 直接修改 `.codespace/`**。`.codespace/` 是工具维护的基准代码,不是开发区;应用代码改动任何模式都必须走 worktree。
|
|
14
14
|
|
|
15
15
|
## 目录结构与职责
|
|
16
16
|
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
| `workspace-config.json` | 关联应用清单,所有应用联动的唯一数据源 | 人工编辑 |
|
|
20
20
|
| `.codespace/` | 各关联应用的基准代码(每个应用一个子目录) | `cbb setup` 与 worktree 流程自动 clone / fetch;**勿手动编辑**;已 gitignore,不提交 |
|
|
21
21
|
| `.worktrees/` | 需求隔离开发区(每个需求一个 `worktree-<需求名>/` 目录) | worktree 命令自动创建 / 清理;不提交(首次执行 worktree 流程时自动加入 .gitignore) |
|
|
22
|
-
| `openspec/` | OpenSpec 工作流配置(`config.yaml` + `schemas/`) | 由 cbb
|
|
22
|
+
| `openspec/` | OpenSpec 工作流配置(`config.yaml` + `schemas/`) | 由 cbb 安装;变更产物(`openspec/changes/` 等)worktree 模式在**需求工作树内**生成随需求分支提交,轻量模式直接在空间根生成提交 main |
|
|
23
23
|
| `.claude/` `.qoder/` `.opencode/` `.codebuddy/` `.trae/` | AI 工具适配目录:编码规范规则、OpenSpec 命令、worktree 管理技能(skill 形态) | 由 cbb 按 setup 时选择的工具安装;已 gitignore,不提交,勿手动改 |
|
|
24
24
|
| `.cbb/` | cbb 安装清单与状态(`.managed-by-cbb`、`.last-tools`) | 由 cbb 管理;**勿手动编辑** |
|
|
25
25
|
|
|
@@ -28,10 +28,14 @@
|
|
|
28
28
|
```
|
|
29
29
|
本目录:对 AI 说"为 <需求名> 创建工作空间"(或 /cbb-worktree-init <需求名>)
|
|
30
30
|
→ 继续在当前会话开发:写操作命令以 .worktrees/worktree-<需求名>/ 为工作目录
|
|
31
|
-
(/opsx:propose → /opsx:apply → /opsx:verify
|
|
32
|
-
→ 说"提交并推送"(或 /cbb-worktree-push)→
|
|
31
|
+
(/opsx:propose → /opsx:apply → /opsx:verify)
|
|
32
|
+
→ 说"提交并推送"(或 /cbb-worktree-push)→ 结尾会提案生成 MR 预填链接(提往测试分支)
|
|
33
|
+
→ 测试环境验收通过 → /opsx:archive → 正式合并走公司流程
|
|
34
|
+
→ 全链路完成后说"关闭工作空间"(或 /cbb-worktree-close)
|
|
33
35
|
```
|
|
34
36
|
|
|
37
|
+
> 轻量模式(需求完全不涉及应用代码)跳过上述流程:空间根直接 `/opsx:*` 全程 → 提交 main → 立即 push。
|
|
38
|
+
|
|
35
39
|
> worktree 管理三件套(init / close / push)以 **skill 形态**分发:既能被 AI 在对话中按意图**自动触发**,也能用斜杠命令手动指定(`/cbb-worktree-init` 等,各工具一致)。OpenSpec 命令为 `/opsx:propose`(opencode 扁平化为 `/opsx-propose`)。
|
|
36
40
|
|
|
37
41
|
---
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jspg-ai/coding-bb",
|
|
3
|
-
"version": "0.0.3-beta.
|
|
3
|
+
"version": "0.0.3-beta.14",
|
|
4
4
|
"description": "整合业界热门且高价值的工具、框架与技能,为 AI CODING AGENT 提供统一的行为准则与工作流,辅助开发者将需求高效落地为符合规范的代码",
|
|
5
5
|
"main": "cbb/lib/install/init.js",
|
|
6
6
|
"bin": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"scripts": {
|
|
20
20
|
"sync:shared": "node scripts/sync-shared.js",
|
|
21
21
|
"pretest": "node scripts/sync-shared.js --check",
|
|
22
|
-
"test": "node test/lib/install/cleanup.test.js && node test/lib/utils/settings.test.js && node test/lib/utils/gitignore.test.js && node test/lib/utils/version.test.js && node test/lib/install/workspaces.test.js && node test/lib/utils/checkbox.test.js && node test/lib/utils/output.test.js && node test/lib/install/cli-help.test.js && node test/lib/install/adapters.test.js && node test/lib/wiki/wiki-publish.test.js && node test/lib/wiki/wiki-parse-args.test.js && node test/lib/wiki/wiki-split.test.js && node test/lib/superpowers/superpowers.test.js && node test/worktrees/push-branches.test.js && node test/worktrees/commit-worktrees.test.js && node test/worktrees/push-worktrees.test.js && node test/worktrees/check-env-deep.test.js && node test/worktrees/create-worktrees.test.js && node test/lib/openspec/protected-paths.test.js"
|
|
22
|
+
"test": "node test/lib/install/cleanup.test.js && node test/lib/utils/settings.test.js && node test/lib/utils/gitignore.test.js && node test/lib/utils/version.test.js && node test/lib/install/workspaces.test.js && node test/lib/utils/checkbox.test.js && node test/lib/utils/output.test.js && node test/lib/install/cli-help.test.js && node test/lib/install/adapters.test.js && node test/lib/wiki/wiki-publish.test.js && node test/lib/wiki/wiki-parse-args.test.js && node test/lib/wiki/wiki-split.test.js && node test/lib/superpowers/superpowers.test.js && node test/worktrees/push-branches.test.js && node test/worktrees/commit-worktrees.test.js && node test/worktrees/push-worktrees.test.js && node test/worktrees/check-env-deep.test.js && node test/worktrees/create-worktrees.test.js && node test/worktrees/make-mr-urls.test.js && node test/lib/openspec/protected-paths.test.js"
|
|
23
23
|
},
|
|
24
24
|
"keywords": [
|
|
25
25
|
"cbb",
|