@jspg-ai/coding-bb 0.0.5-beta.3 → 0.0.5-beta.5
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 +6 -32
- package/cbb/dev-standards/rules/cbb-coding-rule.md +1 -2
- package/cbb/dev-standards/rules/cbb-priority.md +3 -10
- package/cbb/dev-standards/rules/cbb-workspace-discipline.md +13 -20
- package/cbb/dev-standards/skills/coding-specs/SKILL.md +1 -0
- package/cbb/worktrees/_shared/scripts/deprecate-branches.js +67 -2
- package/cbb/worktrees/_shared/scripts/return-to-trunk.js +8 -1
- package/cbb/worktrees/skills/cbb-local-mode/SKILL.md +41 -8
- package/cbb/worktrees/skills/cbb-local-mode/scripts/deprecate-branches.js +67 -2
- package/cbb/worktrees/skills/cbb-local-mode/scripts/return-to-trunk.js +8 -1
- package/cbb/worktrees/skills/cbb-worktree-close/scripts/deprecate-branches.js +67 -2
- package/package.json +1 -1
|
@@ -34,15 +34,8 @@ trigger: always_on
|
|
|
34
34
|
|
|
35
35
|
**只改必须改的,只清理自己造成的烂摊子。**
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
-
|
|
39
|
-
- 不重构没坏的东西
|
|
40
|
-
- 匹配现有风格,即使你更习惯另一种写法
|
|
41
|
-
- 发现无关的死代码,口头提及即可,不要删除
|
|
42
|
-
|
|
43
|
-
当你的改动产生孤儿代码时:
|
|
44
|
-
- 删除因你的改动而不再使用的 import / 变量 / 函数
|
|
45
|
-
- 不删除已有的死代码,除非用户要求
|
|
37
|
+
- 编辑已有代码:不顺手"改进"相邻代码/注释/格式,不重构没坏的东西,匹配现有风格(即使你更习惯另一种写法);发现无关死代码只口头提及,不删除
|
|
38
|
+
- 改动产生孤儿代码:删除因你改动而不再使用的 import / 变量 / 函数;不碰原有死代码,除非用户要求
|
|
46
39
|
|
|
47
40
|
**检验标准:** 每一行改动都应该能追溯到用户的需求。
|
|
48
41
|
|
|
@@ -50,20 +43,7 @@ trigger: always_on
|
|
|
50
43
|
|
|
51
44
|
**定义成功标准,循环直到验证通过。**
|
|
52
45
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
| 而非… | 转换为… |
|
|
56
|
-
|--------|---------|
|
|
57
|
-
| "加个校验" | "先写无效输入的测试,再让测试通过" |
|
|
58
|
-
| "修这个 bug" | "先写能复现的测试,再修到测试通过" |
|
|
59
|
-
| "重构 X" | "确保重构前后测试全部通过" |
|
|
60
|
-
|
|
61
|
-
多步任务先给简短计划:
|
|
62
|
-
```
|
|
63
|
-
1. [步骤] → 验证: [检查项]
|
|
64
|
-
2. [步骤] → 验证: [检查项]
|
|
65
|
-
3. [步骤] → 验证: [检查项]
|
|
66
|
-
```
|
|
46
|
+
将命令式任务转换为可验证的目标——"加个校验"→"先写无效输入的测试再让通过";"修这个 bug"→"先写复现测试再修到通过";"重构 X"→"确保重构前后测试全部通过"。多步任务先给简短计划(步骤 → 验证项)。
|
|
67
47
|
|
|
68
48
|
清晰的成功标准让 AI 可以独立循环验证,模糊的标准("搞一下")需要反复澄清。
|
|
69
49
|
|
|
@@ -92,13 +72,7 @@ trigger: always_on
|
|
|
92
72
|
|
|
93
73
|
**任何实现动作前,先陈述方案并获得用户批准。**
|
|
94
74
|
|
|
95
|
-
-
|
|
96
|
-
-
|
|
97
|
-
- "太简单不需要方案"是最危险的念头——简单意味着方案短,而不是没有方案
|
|
98
|
-
- 中途发现复杂度超出预期:停下来重新说明,不要闷头扩大改动范围
|
|
99
|
-
|
|
100
|
-
**检验标准:** 用户从未因"AI 直接改了代码没打招呼"而感到意外。
|
|
101
|
-
|
|
102
|
-
---
|
|
75
|
+
- 动手前说出打算怎么做(方案、涉及文件、验证方式),等用户确认;仪式随任务缩放:小改动两三句话说清,大改动走 openspec 工作流(propose → specs → design)
|
|
76
|
+
- "太简单不需要方案"是最危险的念头——简单意味着方案短,而不是没有方案;中途发现复杂度超出预期:停下来重新说明,不要闷头扩大改动范围
|
|
103
77
|
|
|
104
|
-
|
|
78
|
+
**检验标准:** 用户从未因"AI 直接改了代码没打招呼"而感到意外。
|
|
@@ -8,7 +8,7 @@ trigger: always_on
|
|
|
8
8
|
- 方法禁止超过 **100 行**,超过必须拆分为语义明确的子方法
|
|
9
9
|
- 主方法只做步骤编排,涉及业务规则判断、状态变更或外部调用的循环/分支逻辑必须抽取为独立方法;仅做参数校验、数据组装、简单赋值等非业务逻辑可保留
|
|
10
10
|
- 命名贴合业务含义,同一工程避免同名类,同一概念命名一致,禁止望文不知义的缩写(反例:`aeh` 表示"运费估算处理")
|
|
11
|
-
-
|
|
11
|
+
- 类注释必须包含:说明、作者、相关类
|
|
12
12
|
- 公共方法(public、REST、RPC)注释必须包含:入参要求和取值范围、出参结果、异常场景;私有/内部方法只要求写核心功能说明
|
|
13
13
|
- 数据模型必须使用对应后缀:`Request`(入参)、`Response`(出参)、`DTO`(服务间传输)、`PO`(持久化对象)、`Param`(查询/操作参数)
|
|
14
14
|
|
|
@@ -28,7 +28,6 @@ trigger: always_on
|
|
|
28
28
|
- 密码、密钥等敏感信息必须存储在 Lion 配置中心,禁止硬编码
|
|
29
29
|
- 日志中禁止输出未脱敏的敏感信息
|
|
30
30
|
- 后台敏感操作(删除/修改)必须记录审计日志:操作人、时间、操作内容,有请求上下文时记录来源 IP
|
|
31
|
-
- 三方资源限制使用频率
|
|
32
31
|
|
|
33
32
|
## 五. 数据库规范
|
|
34
33
|
|
|
@@ -31,20 +31,13 @@ trigger: always_on
|
|
|
31
31
|
2. **识别架构模式** — 确认项目使用的分包模式、命名约定、注解风格
|
|
32
32
|
3. **保持一致性** — 新代码遵循项目风格,而非套用全局规范
|
|
33
33
|
|
|
34
|
-
> 目录内风格不一致时,以目录内多数文件的风格为准;仍无法判断时,以项目显式规范为准。
|
|
35
|
-
|
|
36
34
|
### 冲突处理
|
|
37
35
|
|
|
38
36
|
| 场景 | 处理方式 |
|
|
39
37
|
|------|----------|
|
|
40
|
-
|
|
|
41
|
-
|
|
|
42
|
-
|
|
|
43
|
-
| 项目内存在多种风格混用 | 以当前文件风格为准,不引入第三种风格;当前文件无明确风格时以目录内多数文件风格为准 |
|
|
44
|
-
| 项目无先例可循(如首次使用 Redis、首次写 MQ 消费者) | 使用本规范库作为默认起点 |
|
|
45
|
-
| 全局规范更新 | 以项目实际安装的版本为准,不自动覆盖 |
|
|
46
|
-
| 全新项目 | 使用本规范库作为起点 |
|
|
47
|
-
| 已有项目中的新模块 | 沿用项目现有约定,不另起风格 |
|
|
38
|
+
| 项目已有约定(分层架构 / 命名风格 / 中间件选型 / 新模块) | 一律沿用,不强制统一、不自创风格、不强行切换 |
|
|
39
|
+
| 项目内存在多种风格混用 | 以当前文件风格为准,不引入第三种风格;当前文件无明确风格时取目录内多数文件风格,仍有歧义以项目显式规范为准 |
|
|
40
|
+
| 项目无先例可循 / 全新项目(如首次使用 Redis、首次写 MQ 消费者) | 使用本规范库作为默认起点 |
|
|
48
41
|
|
|
49
42
|
> **显式规范与代码风格冲突时的处理**:显式规范优先用于新代码;不要求对已有代码做大规模 reformat。判断标准:涉及命名约定、架构分层、中间件选型等**结构性决策**时以显式规范为准;仅涉及缩进、换行等**格式细节**时以当前文件风格为准。
|
|
50
43
|
|
|
@@ -4,35 +4,28 @@ trigger: always_on
|
|
|
4
4
|
|
|
5
5
|
# 业务空间开发纪律(双模式,用户选择制)
|
|
6
6
|
|
|
7
|
-
**仅当处于业务空间体系(空间根、`.worktrees/`
|
|
7
|
+
**仅当处于业务空间体系(空间根、`.worktrees/` 工作树、及其中各应用检出)时生效。空间支持本地模式 / worktree 模式两种开发模式,能力对称(ADR 0007/0008),由用户显式选择,agent 只给推荐不代决。执行细则以技能为唯一出处:本地模式 `cbb-local-mode`;worktree 全流程 `cbb-worktree-init` / `cbb-worktree-push` / `cbb-worktree-close`。**
|
|
8
8
|
|
|
9
|
-
**模式一 · 本地模式(单线程快改,当前会话就地干):**
|
|
9
|
+
**模式一 · 本地模式(单线程快改,当前会话就地干):** 根 + 各涉及应用 `.codespace/<app>/` 就地切同名分支(基线 = `origin/<主干>`),当前会话原地开发,相对路径原样生效;**一次只承载一个需求**(独占本地检出;并行新需求走 worktree 模式,互不干扰)。收尾受下「收尾合并门禁」约束。
|
|
10
10
|
|
|
11
|
-
-
|
|
12
|
-
- 当前会话原地开发,相对路径原样生效;**一次只承载一个需求**(独占本地检出;期间并行新需求走 worktree 模式,互不干扰)
|
|
13
|
-
- 收尾(收尾合并门禁,ADR 0007):`merge-branches.js --check` 判定已合并;未合并须三选一(提 MR / 直合并(临时 worktree 零接触,ADR 0008) / 确认废弃)后方可收尾;交付完成后各参与仓库回主干并删分支(`return-to-trunk.js` + `delete-branches.js`,主干名经远端 HEAD 探测,兼容 main / master)
|
|
14
|
-
|
|
15
|
-
**模式二 · worktree 模式(并行 / 隔离):**
|
|
16
|
-
|
|
17
|
-
- `cbb-worktree-init` 建隔离 worktree(AI 配置内容子目录已 junction 就位),**开发会话开进 `.worktrees/worktree-<需求名>/`**,可多需求并行;app 检出收拢在 `<wt>/.codespace/<app>/`——**镜像空间根布局**(`.codespace/<app>/...` 相对路径两模式通用,ADR 0003)
|
|
18
|
-
- 根会话保留作控制面(worktree 生灭、`cbb update` 等空间级操作);**会话形态按工具定,能自动则 agent 亲自做**:OpenCode 桌面端 = agent 自动把会话切进/切出 worktree(`session_move`,上下文保留、不列入用户待办,空间根路径在案供回切);无会话搬移能力的桌面端 = 用户手动切根;CLI/TUI = 新开会话进 worktree
|
|
11
|
+
**模式二 · worktree 模式(并行 / 隔离):** `cbb-worktree-init` 建隔离 worktree,**开发会话开进 `.worktrees/worktree-<需求名>/`**,可多需求并行;app 检出收拢在 `<wt>/.codespace/<app>/`——**镜像空间根布局**(`.codespace/<app>/...` 相对路径两模式通用,ADR 0003)。根会话保留作控制面(worktree 生灭、`cbb update` 等空间级操作);会话切进/切出的工具差异与细则见 init / close 技能。
|
|
19
12
|
|
|
20
13
|
**基准与防线(两模式共同,ADR 0002):**
|
|
21
14
|
|
|
22
|
-
- **基准 = `origin
|
|
23
|
-
-
|
|
24
|
-
- 需求分支推送唯一出口 = `cbb-worktree-push`(init 创建流程零远端操作,lazy push);推送后可选提 MR 或**直接合并**(`merge-branches.js`,主干 / 测试分支,ADR 0008
|
|
25
|
-
- **收尾合并门禁(ADR 0007)**:两模式收尾均须每仓库"已合并"(`safety-check` / `merge-branches.js --check` 判定);未合并三选一——提 MR / 直合并 / 确认废弃(远程重命名 `<分支>-deprecated
|
|
15
|
+
- **基准 = `origin/<主干>`(唯一真相源);`.codespace/<app>/` 的检出 = 视图**——本地模式可暂态被需求分支占用,收尾恢复
|
|
16
|
+
- **严禁直接在任何主干检出上提交载荷**:分支感知 pre-commit hook 机械拦截(workspace 仓库仅放行控制面白名单:`CBB-WORKSPACE.json` / `.gitignore` / `AGENTS.md` / `openspec/config.yaml` / `openspec/schemas/**`;应用仓库主干全拦;**merge 提交(`MERGE_HEAD` 存在)放行**,见 ADR 0007)——**被拦是防线生效,不是故障**;`--no-verify` 绕过仅限用户显式要求
|
|
17
|
+
- 需求分支推送唯一出口 = `cbb-worktree-push`(init 创建流程零远端操作,lazy push);推送后可选提 MR 或**直接合并**(`merge-branches.js`,主干 / 测试分支,ADR 0008)
|
|
18
|
+
- **收尾合并门禁(ADR 0007)**:两模式收尾均须每仓库"已合并"(`safety-check` / `merge-branches.js --check` 判定);未合并三选一——提 MR / 直合并 / 确认废弃(远程重命名 `<分支>-deprecated`);交付完成回主干并自动删本地+远程分支,**废弃出口保留本地分支**(细则见 cbb-local-mode Step 5 / cbb-worktree-close Step 3.4)
|
|
26
19
|
- **模式误判零代价**:本地模式中途要并行/隔离 → 重跑 `cbb-worktree-init`(分支已存在自动复用,提交随身带走)
|
|
27
20
|
|
|
28
|
-
**五时刻对齐(agent
|
|
21
|
+
**五时刻对齐(agent 必须遵守;1–3 的细则见 `cbb-local-mode` 技能):**
|
|
29
22
|
|
|
30
|
-
1.
|
|
23
|
+
1. **开工前——状态自检**:干净态零交互直接开工;**遗留态(别的需求未收尾,任一仓库检出在非主干分支)必须让用户四选一**(继续 / 先收尾 / 升级 worktree / 丢弃),不得自行决定
|
|
31
24
|
2. **开工时——布局确认**:向用户展示参与仓库与分支名,确认后执行
|
|
32
25
|
3. **进行中——插单归属**:用户插入非本需求的改动,先确认归属(并入 / 收尾后处理 / 转 worktree),不得静默混入
|
|
33
|
-
4.
|
|
34
|
-
5.
|
|
26
|
+
4. **打包前——主干对齐**(任何构建打包动作前):产物涉及哪些仓库(按代码来源判定,与产物存放位置无关;多仓聚合则全部)就先同步哪些——① `git fetch <远端>`;② 在当前需求分支 `git merge <远端>/<主干>`(主干名以远端 HEAD 为准;在主干检出上打包改为 `git pull`;未提交修改阻碍合并时先提交或暂存);③ 冲突未解决**禁止打包**,停下报告用户;fetch/merge 失败 → 报告并等待指示;④ 对齐完成(merge 提交属正常)再打包;产物目录须已被 `.gitignore` 忽略(未忽略先补,随需求分支提交)。产物基线 = 主干最新;**不 rebase、不 force push**,merge 提交随下次 push 一并推送
|
|
27
|
+
5. **打包后——产物收集与留存**:把各仓库产物**复制**到 `<空间根>/.build/<需求名>/<应用名>/`(原件原位;需求名 = 分支名;workspace 用 `workspace` 子目录;目标已存在先向用户确认清理或覆盖;`.build/` 由 cbb 写入空间根 `.gitignore`)——按需求聚合留存,worktree 关闭后不丢失;打包时顺带报告 `.build/` 积压现状(需求目录数与总体积),建议清理已交付/废弃需求的产物目录,经用户逐项确认后执行、**永不自动删除**
|
|
35
28
|
|
|
36
|
-
**读基准代码约定:**
|
|
29
|
+
**读基准代码约定:** 本地模式进行中,工作区是开发态而非基准——读"主干现状"须从主干显式获取;不要默认工作区即基准(细则见 `cbb-local-mode` 技能)。
|
|
37
30
|
|
|
38
|
-
**检验标准:**
|
|
31
|
+
**检验标准:** 任何主干检出上无载荷提交(hook 兜底,git log 可机械审计;merge 提交除外);本地模式收尾后全部仓库回主干且 clean;worktree 模式的变更(代码改动)只出现在需求工作树内(打包产物按第 5 条收集到 `.build/`,不在此限);需求分支只经 push 技能产生;**未合并且非废弃的分支无法通过收尾(门禁兜底)**。
|
|
@@ -9,9 +9,17 @@ require('./silence-popup');
|
|
|
9
9
|
*
|
|
10
10
|
* 用法:
|
|
11
11
|
* node deprecate-branches.js <ws_root> <branch> [--apps a,b]
|
|
12
|
+
* node deprecate-branches.js <ws_root> <branch> [--apps a,b] --check # 仅检查,不重命名
|
|
13
|
+
*
|
|
14
|
+
* 模式:
|
|
15
|
+
* --check 只读检查:逐仓库报远端分支存在性与本地未推送提交数
|
|
16
|
+
* (ls-remote 取远端权威 hash 与本地 <branch> 比对)。
|
|
17
|
+
* 用途:废弃重命名只覆盖远端已推送的部分——本地 ahead 提交
|
|
18
|
+
* 不会被带进 <branch>-deprecated,执行前须提示用户决策。
|
|
19
|
+
* 默认 执行远端重命名 <branch> → <branch>-deprecated
|
|
12
20
|
*
|
|
13
21
|
* 退出码:0 全部成功(含 skipped)/ 1 参数错误 / 2 存在失败
|
|
14
|
-
* 末行:DEPRECATE_RESULT [...]
|
|
22
|
+
* 末行:DEPRECATE_RESULT [...] 或 DEPRECATE_CHECK_RESULT [...]
|
|
15
23
|
*/
|
|
16
24
|
|
|
17
25
|
const { execSync } = require('child_process');
|
|
@@ -44,6 +52,51 @@ function loadApps(wsRoot) {
|
|
|
44
52
|
}
|
|
45
53
|
}
|
|
46
54
|
|
|
55
|
+
/**
|
|
56
|
+
* 检查单个仓库(--check,只读):
|
|
57
|
+
* unpushed: 0 = 已全部推送;>0 = 本地领先远端 N 个提交;'all' = 远端无该分支(提交全部未推送)
|
|
58
|
+
*/
|
|
59
|
+
function checkRepo(name, repo, branch) {
|
|
60
|
+
const rec = { name, status: 'skipped', remote_exists: false, unpushed: 0, detail: null };
|
|
61
|
+
if (!fs.existsSync(path.join(repo, '.git'))) {
|
|
62
|
+
rec.detail = 'no-repo';
|
|
63
|
+
return rec;
|
|
64
|
+
}
|
|
65
|
+
const remote = run('git remote', repo);
|
|
66
|
+
if (!remote.ok || !remote.out) {
|
|
67
|
+
rec.detail = 'no-remote';
|
|
68
|
+
return rec;
|
|
69
|
+
}
|
|
70
|
+
const r = remote.out.split(/\r?\n/)[0];
|
|
71
|
+
if (!run('git rev-parse --verify --quiet "refs/heads/' + branch + '"', repo).ok) {
|
|
72
|
+
rec.detail = 'no-local-branch';
|
|
73
|
+
return rec;
|
|
74
|
+
}
|
|
75
|
+
const ls = run('git ls-remote --heads "' + r + '" "refs/heads/' + branch + '"', repo);
|
|
76
|
+
if (!ls.ok) {
|
|
77
|
+
rec.status = 'failed';
|
|
78
|
+
rec.detail = 'ls-remote-failed';
|
|
79
|
+
return rec;
|
|
80
|
+
}
|
|
81
|
+
if (!ls.out) {
|
|
82
|
+
rec.status = 'ok';
|
|
83
|
+
rec.unpushed = 'all';
|
|
84
|
+
rec.detail = 'remote-has-no-branch';
|
|
85
|
+
return rec;
|
|
86
|
+
}
|
|
87
|
+
const remoteHash = ls.out.split(/\r?\n/)[0].split(/\s+/)[0];
|
|
88
|
+
const cnt = run('git rev-list --count "' + remoteHash + '..refs/heads/' + branch + '"', repo);
|
|
89
|
+
if (!cnt.ok) {
|
|
90
|
+
rec.status = 'failed';
|
|
91
|
+
rec.detail = 'rev-list-failed';
|
|
92
|
+
return rec;
|
|
93
|
+
}
|
|
94
|
+
rec.status = 'ok';
|
|
95
|
+
rec.remote_exists = true;
|
|
96
|
+
rec.unpushed = parseInt(cnt.out, 10) || 0;
|
|
97
|
+
return rec;
|
|
98
|
+
}
|
|
99
|
+
|
|
47
100
|
/** 对单个仓库执行废弃重命名 */
|
|
48
101
|
function deprecateRepo(name, repo, branch) {
|
|
49
102
|
const newName = branch + '-deprecated';
|
|
@@ -95,6 +148,7 @@ function main() {
|
|
|
95
148
|
const appFilter = appsIdx >= 0
|
|
96
149
|
? new Set((args[appsIdx + 1] || '').split(',').map(s => s.trim()).filter(Boolean))
|
|
97
150
|
: null;
|
|
151
|
+
const checkOnly = args.includes('--check');
|
|
98
152
|
|
|
99
153
|
const repos = [{ name: 'workspace', repo: wsRoot }];
|
|
100
154
|
for (const app of loadApps(wsRoot)) {
|
|
@@ -102,6 +156,17 @@ function main() {
|
|
|
102
156
|
repos.push({ name: app.name, repo: path.join(wsRoot, '.codespace', app.name) });
|
|
103
157
|
}
|
|
104
158
|
|
|
159
|
+
if (checkOnly) {
|
|
160
|
+
const results = repos.map(rp => checkRepo(rp.name, rp.repo, branch));
|
|
161
|
+
results.forEach(r => {
|
|
162
|
+
const up = r.status === 'ok' && r.unpushed !== 0 ? ' [unpushed: ' + r.unpushed + ']' : '';
|
|
163
|
+
console.log(r.status.toUpperCase() + ' ' + r.name + (r.detail ? ' — ' + r.detail : '') + up);
|
|
164
|
+
});
|
|
165
|
+
console.log('---');
|
|
166
|
+
console.log('DEPRECATE_CHECK_RESULT', JSON.stringify({ branch, results }));
|
|
167
|
+
process.exit(results.some(r => r.status === 'failed') ? 2 : 0);
|
|
168
|
+
}
|
|
169
|
+
|
|
105
170
|
const results = repos.map(rp => deprecateRepo(rp.name, rp.repo, branch));
|
|
106
171
|
|
|
107
172
|
results.forEach(r => {
|
|
@@ -116,4 +181,4 @@ function main() {
|
|
|
116
181
|
|
|
117
182
|
if (require.main === module) main();
|
|
118
183
|
|
|
119
|
-
module.exports = { run, loadApps, deprecateRepo };
|
|
184
|
+
module.exports = { run, loadApps, checkRepo, deprecateRepo };
|
|
@@ -21,7 +21,8 @@ require('./silence-popup');
|
|
|
21
21
|
* - 检出 dirty(含未跟踪)→ FAIL dirty-worktree(列未提交项数;不自动 stash,交用户决策)
|
|
22
22
|
* - 本地有主干分支 → git checkout <主干>;仅远端有 → git checkout -b <主干> --track origin/<主干>
|
|
23
23
|
* 5. 追平远端:origin/<主干> 存在 → git pull --ff-only origin <主干>
|
|
24
|
-
*
|
|
24
|
+
* (成功详情注记 `pulled origin/<主干>` / `up-to-date`;本地主干与 origin 分叉 → FAIL
|
|
25
|
+
* pull-failed,交用户决策;远端无主干 → 注记跳过)
|
|
25
26
|
* 6. 已在主干 → 跳过切回,仍追平(幂等)
|
|
26
27
|
*
|
|
27
28
|
* 退出码:0 全部成功(含 no-repo / 已在主干)/ 1 参数错误 / 2 任一仓库失败
|
|
@@ -160,12 +161,18 @@ function main() {
|
|
|
160
161
|
}
|
|
161
162
|
|
|
162
163
|
if (hasRef(rp.repo, 'refs/remotes/origin/' + main)) {
|
|
164
|
+
const before = run('git rev-parse --quiet HEAD', rp.repo);
|
|
163
165
|
const pull = run('git pull --ff-only --quiet origin "' + main + '"', rp.repo);
|
|
164
166
|
if (!pull.ok) {
|
|
165
167
|
rec.status = 'failed';
|
|
166
168
|
rec.reason = 'pull-failed';
|
|
167
169
|
rec.detail = (pull.err || '').split(/\r?\n/)[0];
|
|
168
170
|
failed = true;
|
|
171
|
+
} else {
|
|
172
|
+
const after = run('git rev-parse --quiet HEAD', rp.repo);
|
|
173
|
+
rec.detail = before.ok && after.ok && before.out !== after.out
|
|
174
|
+
? 'pulled origin/' + main
|
|
175
|
+
: 'up-to-date';
|
|
169
176
|
}
|
|
170
177
|
} else if (!rec.reason) {
|
|
171
178
|
rec.reason = 'no-remote-trunk';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: cbb-local-mode
|
|
3
|
-
description: "业务空间本地模式开发:空间根与各涉及应用在 .codespace/<app>/ 就地建同名分支,当前会话直接开发(不建 worktree、不换目录)。触发:用户说'本地模式'/'就地开发'/'不走 worktree'/'在这个窗口开发',或双模式选择时用户选本地模式。覆盖开工(状态自检→布局确认→切分支)、开发、提交推送(转 cbb-worktree-push)、收尾(收尾合并门禁 ADR 0007:MR / 直合并 /
|
|
3
|
+
description: "业务空间本地模式开发:空间根与各涉及应用在 .codespace/<app>/ 就地建同名分支,当前会话直接开发(不建 worktree、不换目录)。触发:用户说'本地模式'/'就地开发'/'不走 worktree'/'在这个窗口开发',或双模式选择时用户选本地模式。覆盖开工(状态自检→布局确认→切分支)、开发、提交推送(转 cbb-worktree-push)、收尾(收尾合并门禁 ADR 0007:MR / 直合并 / 确认废弃;交付完成回主干 + 删分支,废弃保留本地分支)、升级 worktree(重跑 cbb-worktree-init)。一次只承载一个需求。"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Local Mode(本地模式)
|
|
@@ -103,9 +103,36 @@ node "<skill_dir>/scripts/merge-branches.js" "<ws_root>" "<需求名>" --apps "<
|
|
|
103
103
|
```
|
|
104
104
|
|
|
105
105
|
`need-mr`(远端主干受保护)→ 改用提 MR;`failed` → 输出明细、修复后重跑
|
|
106
|
-
- **确认废弃** → `
|
|
106
|
+
- **确认废弃** → 三步:检查未推送提交 → 二次确认 → 执行。**废弃后仍进 5.4 步骤 1 回主干,但跳过步骤 2(本地分支保留原名,不调用 `delete-branches.js`)**。
|
|
107
107
|
|
|
108
|
-
|
|
108
|
+
1. 检查未推送提交(废弃重命名只覆盖远端已推送的部分,本地 ahead 提交不会进 `<需求名>-deprecated`):
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
node "<skill_dir>/scripts/deprecate-branches.js" "<ws_root>" "<需求名>" --apps "<app1,app2>" --check
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**解读末行 `DEPRECATE_CHECK_RESULT`**:每仓库 `unpushed`(本地领先远端的提交数;`all` = 远端无该分支、提交全部未推送;`0` = 已全部推送)。存在 `unpushed > 0` / `all` 时用 `AskUserQuestion`:
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
⚠️ 以下仓库存在未推送提交(将只保留在本地分支,不进 <需求名>-deprecated):
|
|
118
|
+
- workspace: 3 个 commit
|
|
119
|
+
|
|
120
|
+
请选择处理方式:
|
|
121
|
+
[先推送](推荐)调 cbb-worktree-push 推送全部提交后回到本步
|
|
122
|
+
[继续] 不推送直接废弃(这些提交只存在于被保留的本地分支)
|
|
123
|
+
[中止] 终止流程,我先手动处理
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
2. 二次确认(远程重命名不可逆,`AskUserQuestion` 固定选项):确认废弃 / 中止
|
|
127
|
+
3. 执行:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
node "<skill_dir>/scripts/deprecate-branches.js" "<ws_root>" "<需求名>" --apps "<app1,app2>"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
`failed` → 报告并终止;完成后进 5.4(**仅步骤 1**)
|
|
134
|
+
|
|
135
|
+
**5.4 交付完成 → 各仓库回主干并删分支**(脚本化;主干名逐仓库经远端 HEAD 探测,兼容 main / master。**废弃出口仅执行步骤 1**——分支保留、不删):
|
|
109
136
|
|
|
110
137
|
1. 各仓库切回主干并追平远端(替代原裸 git 三连;不自动 stash,失败即报告):
|
|
111
138
|
|
|
@@ -114,20 +141,20 @@ node "<skill_dir>/scripts/return-to-trunk.js" "<ws_root>" --apps "<app1,app2>"
|
|
|
114
141
|
```
|
|
115
142
|
|
|
116
143
|
**解读末行 `TRUNK_RESULT`**:
|
|
117
|
-
- 退出码 0 →
|
|
144
|
+
- 退出码 0 → 全部已回主干并追平(detail 注记 `pulled origin/<主干>` = 本次有追平;`up-to-date` = 本就最新)
|
|
118
145
|
- 某仓库 `dirty-worktree` → 检出上有未提交改动(回归 5.1 前置,先处理再重跑);**不自动 stash**
|
|
119
146
|
- 某仓库 `pull-failed`(本地主干与 origin 分叉)→ 输出详情,交用户决策
|
|
120
147
|
- 某仓库 `cannot-detect-main` → 检查该仓库远端与主干分支(`fetch-failed` 同理)
|
|
121
148
|
- 幂等:已在主干的仓库仅追平远端,重复执行无害
|
|
122
149
|
|
|
123
|
-
2. 本地 +
|
|
150
|
+
2. 本地 + 远程需求分支删除(**仅交付完成出口执行**——MR 已合入 / 直合并成功;**废弃出口跳过本步**,本地分支保留原名。本地删除含 squash 合并的 `-d` → `-D` 兜底;删除前输出将删清单):
|
|
124
151
|
|
|
125
152
|
```bash
|
|
126
153
|
node "<skill_dir>/scripts/delete-branches.js" "<ws_root>" "<需求名>" local_remote
|
|
127
154
|
```
|
|
128
155
|
|
|
129
156
|
- openspec change 归档在合并前执行(产物在分支上随交付合入)
|
|
130
|
-
-
|
|
157
|
+
- 全部仓库回主干(交付完成出口含分支删除)后,向用户报告"本地模式已收尾,空间回到干净态"
|
|
131
158
|
|
|
132
159
|
## 升级 worktree(中途要并行 / 要隔离)
|
|
133
160
|
|
|
@@ -146,7 +173,7 @@ node "<skill_dir>/scripts/delete-branches.js" "<ws_root>" "<需求名>" local_re
|
|
|
146
173
|
| commit 被 hook 拦(main 载荷) | 大声报错是预期行为:回分支重做;禁止自动 --no-verify |
|
|
147
174
|
| 误在 main 检出上写了文件 | `git checkout -b <需求名>` 后改动随分支走;已 staged 则 unstage 后再切 |
|
|
148
175
|
| 提交推送 | 说"提交并推送" → cbb-worktree-push(本地布局自动发现) |
|
|
149
|
-
| 收尾 | 收尾合并门禁(ADR 0007):`merge-branches.js --check` 判已合并 → 全合并则 `return-to-trunk.js` 回主干 + `delete-branches.js` 删分支(主干名探测);否则三选一(MR / 直合并(临时 worktree) /
|
|
176
|
+
| 收尾 | 收尾合并门禁(ADR 0007):`merge-branches.js --check` 判已合并 → 全合并则 `return-to-trunk.js` 回主干 + `delete-branches.js` 删分支(主干名探测);否则三选一(MR / 直合并(临时 worktree) / 确认废弃(仅回主干不删分支)) |
|
|
150
177
|
| 要并行新需求 | 转 worktree 模式(init 分支从 origin/main 建,不占本地检出);本地需求继续占用根 |
|
|
151
178
|
| 升级隔离 | 重跑 cbb-worktree-init(提交随身带走);先回主干腾检出(return-to-trunk.js) |
|
|
152
179
|
|
|
@@ -174,6 +201,11 @@ node "<skill_dir>/scripts/delete-branches.js" "<ws_root>" "<需求名>" local_re
|
|
|
174
201
|
- **问题:** 回主干后未追平远端,本地主干陈旧,下次 `start-local` 从旧基线建分支
|
|
175
202
|
- **修复:** 收尾固定两步脚本:`return-to-trunk.js`(切回主干 + `pull --ff-only` 追平)→ `delete-branches.js` 删分支
|
|
176
203
|
|
|
204
|
+
### 废弃出口误跑删分支
|
|
205
|
+
|
|
206
|
+
- **问题:** 选"确认废弃"后照 5.4 全跑,`delete-branches.js` 对未合并的废弃分支 `-D` 强删——远端 `<需求名>-deprecated` 只含已推送部分,本地领先提交只剩 reflog 可捞
|
|
207
|
+
- **修复:** 废弃出口仅执行 5.4 步骤 1(回主干 + 追平);本地分支保留原名,**不调用** `delete-branches.js`
|
|
208
|
+
|
|
177
209
|
### 中途插单静默混入
|
|
178
210
|
|
|
179
211
|
- **问题:** 用户插的"顺手改一下"与本需求改动混在同一 commit,评审困难
|
|
@@ -200,5 +232,6 @@ node "<skill_dir>/scripts/delete-branches.js" "<ws_root>" "<需求名>" local_re
|
|
|
200
232
|
- 切分支统一走 `start-local.js`(基线 = `<remote>/<main>`,不取 HEAD)
|
|
201
233
|
- 收尾先过门禁判定(`merge-branches.js --check`);未合并必须三选一后收尾
|
|
202
234
|
- 直合并走 `merge-branches.js`(临时 worktree,零接触;本地检出由 5.4 `return-to-trunk.js` 切回主干)
|
|
203
|
-
-
|
|
235
|
+
- 废弃出口:先 `deprecate-branches.js --check` 查未推送提交(有 ahead 须用户决策)→ 二次确认后执行;废弃后**仅回主干**(5.4 步骤 1),不删本地分支
|
|
236
|
+
- 收尾脚本化(`return-to-trunk.js` 回主干 + 交付完成出口 `delete-branches.js` 删分支)完成后报告干净态
|
|
204
237
|
- 所有写操作前的分支确认(当前检出 = 需求分支)
|
|
@@ -9,9 +9,17 @@ require('./silence-popup');
|
|
|
9
9
|
*
|
|
10
10
|
* 用法:
|
|
11
11
|
* node deprecate-branches.js <ws_root> <branch> [--apps a,b]
|
|
12
|
+
* node deprecate-branches.js <ws_root> <branch> [--apps a,b] --check # 仅检查,不重命名
|
|
13
|
+
*
|
|
14
|
+
* 模式:
|
|
15
|
+
* --check 只读检查:逐仓库报远端分支存在性与本地未推送提交数
|
|
16
|
+
* (ls-remote 取远端权威 hash 与本地 <branch> 比对)。
|
|
17
|
+
* 用途:废弃重命名只覆盖远端已推送的部分——本地 ahead 提交
|
|
18
|
+
* 不会被带进 <branch>-deprecated,执行前须提示用户决策。
|
|
19
|
+
* 默认 执行远端重命名 <branch> → <branch>-deprecated
|
|
12
20
|
*
|
|
13
21
|
* 退出码:0 全部成功(含 skipped)/ 1 参数错误 / 2 存在失败
|
|
14
|
-
* 末行:DEPRECATE_RESULT [...]
|
|
22
|
+
* 末行:DEPRECATE_RESULT [...] 或 DEPRECATE_CHECK_RESULT [...]
|
|
15
23
|
*/
|
|
16
24
|
|
|
17
25
|
const { execSync } = require('child_process');
|
|
@@ -44,6 +52,51 @@ function loadApps(wsRoot) {
|
|
|
44
52
|
}
|
|
45
53
|
}
|
|
46
54
|
|
|
55
|
+
/**
|
|
56
|
+
* 检查单个仓库(--check,只读):
|
|
57
|
+
* unpushed: 0 = 已全部推送;>0 = 本地领先远端 N 个提交;'all' = 远端无该分支(提交全部未推送)
|
|
58
|
+
*/
|
|
59
|
+
function checkRepo(name, repo, branch) {
|
|
60
|
+
const rec = { name, status: 'skipped', remote_exists: false, unpushed: 0, detail: null };
|
|
61
|
+
if (!fs.existsSync(path.join(repo, '.git'))) {
|
|
62
|
+
rec.detail = 'no-repo';
|
|
63
|
+
return rec;
|
|
64
|
+
}
|
|
65
|
+
const remote = run('git remote', repo);
|
|
66
|
+
if (!remote.ok || !remote.out) {
|
|
67
|
+
rec.detail = 'no-remote';
|
|
68
|
+
return rec;
|
|
69
|
+
}
|
|
70
|
+
const r = remote.out.split(/\r?\n/)[0];
|
|
71
|
+
if (!run('git rev-parse --verify --quiet "refs/heads/' + branch + '"', repo).ok) {
|
|
72
|
+
rec.detail = 'no-local-branch';
|
|
73
|
+
return rec;
|
|
74
|
+
}
|
|
75
|
+
const ls = run('git ls-remote --heads "' + r + '" "refs/heads/' + branch + '"', repo);
|
|
76
|
+
if (!ls.ok) {
|
|
77
|
+
rec.status = 'failed';
|
|
78
|
+
rec.detail = 'ls-remote-failed';
|
|
79
|
+
return rec;
|
|
80
|
+
}
|
|
81
|
+
if (!ls.out) {
|
|
82
|
+
rec.status = 'ok';
|
|
83
|
+
rec.unpushed = 'all';
|
|
84
|
+
rec.detail = 'remote-has-no-branch';
|
|
85
|
+
return rec;
|
|
86
|
+
}
|
|
87
|
+
const remoteHash = ls.out.split(/\r?\n/)[0].split(/\s+/)[0];
|
|
88
|
+
const cnt = run('git rev-list --count "' + remoteHash + '..refs/heads/' + branch + '"', repo);
|
|
89
|
+
if (!cnt.ok) {
|
|
90
|
+
rec.status = 'failed';
|
|
91
|
+
rec.detail = 'rev-list-failed';
|
|
92
|
+
return rec;
|
|
93
|
+
}
|
|
94
|
+
rec.status = 'ok';
|
|
95
|
+
rec.remote_exists = true;
|
|
96
|
+
rec.unpushed = parseInt(cnt.out, 10) || 0;
|
|
97
|
+
return rec;
|
|
98
|
+
}
|
|
99
|
+
|
|
47
100
|
/** 对单个仓库执行废弃重命名 */
|
|
48
101
|
function deprecateRepo(name, repo, branch) {
|
|
49
102
|
const newName = branch + '-deprecated';
|
|
@@ -95,6 +148,7 @@ function main() {
|
|
|
95
148
|
const appFilter = appsIdx >= 0
|
|
96
149
|
? new Set((args[appsIdx + 1] || '').split(',').map(s => s.trim()).filter(Boolean))
|
|
97
150
|
: null;
|
|
151
|
+
const checkOnly = args.includes('--check');
|
|
98
152
|
|
|
99
153
|
const repos = [{ name: 'workspace', repo: wsRoot }];
|
|
100
154
|
for (const app of loadApps(wsRoot)) {
|
|
@@ -102,6 +156,17 @@ function main() {
|
|
|
102
156
|
repos.push({ name: app.name, repo: path.join(wsRoot, '.codespace', app.name) });
|
|
103
157
|
}
|
|
104
158
|
|
|
159
|
+
if (checkOnly) {
|
|
160
|
+
const results = repos.map(rp => checkRepo(rp.name, rp.repo, branch));
|
|
161
|
+
results.forEach(r => {
|
|
162
|
+
const up = r.status === 'ok' && r.unpushed !== 0 ? ' [unpushed: ' + r.unpushed + ']' : '';
|
|
163
|
+
console.log(r.status.toUpperCase() + ' ' + r.name + (r.detail ? ' — ' + r.detail : '') + up);
|
|
164
|
+
});
|
|
165
|
+
console.log('---');
|
|
166
|
+
console.log('DEPRECATE_CHECK_RESULT', JSON.stringify({ branch, results }));
|
|
167
|
+
process.exit(results.some(r => r.status === 'failed') ? 2 : 0);
|
|
168
|
+
}
|
|
169
|
+
|
|
105
170
|
const results = repos.map(rp => deprecateRepo(rp.name, rp.repo, branch));
|
|
106
171
|
|
|
107
172
|
results.forEach(r => {
|
|
@@ -116,4 +181,4 @@ function main() {
|
|
|
116
181
|
|
|
117
182
|
if (require.main === module) main();
|
|
118
183
|
|
|
119
|
-
module.exports = { run, loadApps, deprecateRepo };
|
|
184
|
+
module.exports = { run, loadApps, checkRepo, deprecateRepo };
|
|
@@ -21,7 +21,8 @@ require('./silence-popup');
|
|
|
21
21
|
* - 检出 dirty(含未跟踪)→ FAIL dirty-worktree(列未提交项数;不自动 stash,交用户决策)
|
|
22
22
|
* - 本地有主干分支 → git checkout <主干>;仅远端有 → git checkout -b <主干> --track origin/<主干>
|
|
23
23
|
* 5. 追平远端:origin/<主干> 存在 → git pull --ff-only origin <主干>
|
|
24
|
-
*
|
|
24
|
+
* (成功详情注记 `pulled origin/<主干>` / `up-to-date`;本地主干与 origin 分叉 → FAIL
|
|
25
|
+
* pull-failed,交用户决策;远端无主干 → 注记跳过)
|
|
25
26
|
* 6. 已在主干 → 跳过切回,仍追平(幂等)
|
|
26
27
|
*
|
|
27
28
|
* 退出码:0 全部成功(含 no-repo / 已在主干)/ 1 参数错误 / 2 任一仓库失败
|
|
@@ -160,12 +161,18 @@ function main() {
|
|
|
160
161
|
}
|
|
161
162
|
|
|
162
163
|
if (hasRef(rp.repo, 'refs/remotes/origin/' + main)) {
|
|
164
|
+
const before = run('git rev-parse --quiet HEAD', rp.repo);
|
|
163
165
|
const pull = run('git pull --ff-only --quiet origin "' + main + '"', rp.repo);
|
|
164
166
|
if (!pull.ok) {
|
|
165
167
|
rec.status = 'failed';
|
|
166
168
|
rec.reason = 'pull-failed';
|
|
167
169
|
rec.detail = (pull.err || '').split(/\r?\n/)[0];
|
|
168
170
|
failed = true;
|
|
171
|
+
} else {
|
|
172
|
+
const after = run('git rev-parse --quiet HEAD', rp.repo);
|
|
173
|
+
rec.detail = before.ok && after.ok && before.out !== after.out
|
|
174
|
+
? 'pulled origin/' + main
|
|
175
|
+
: 'up-to-date';
|
|
169
176
|
}
|
|
170
177
|
} else if (!rec.reason) {
|
|
171
178
|
rec.reason = 'no-remote-trunk';
|
|
@@ -9,9 +9,17 @@ require('./silence-popup');
|
|
|
9
9
|
*
|
|
10
10
|
* 用法:
|
|
11
11
|
* node deprecate-branches.js <ws_root> <branch> [--apps a,b]
|
|
12
|
+
* node deprecate-branches.js <ws_root> <branch> [--apps a,b] --check # 仅检查,不重命名
|
|
13
|
+
*
|
|
14
|
+
* 模式:
|
|
15
|
+
* --check 只读检查:逐仓库报远端分支存在性与本地未推送提交数
|
|
16
|
+
* (ls-remote 取远端权威 hash 与本地 <branch> 比对)。
|
|
17
|
+
* 用途:废弃重命名只覆盖远端已推送的部分——本地 ahead 提交
|
|
18
|
+
* 不会被带进 <branch>-deprecated,执行前须提示用户决策。
|
|
19
|
+
* 默认 执行远端重命名 <branch> → <branch>-deprecated
|
|
12
20
|
*
|
|
13
21
|
* 退出码:0 全部成功(含 skipped)/ 1 参数错误 / 2 存在失败
|
|
14
|
-
* 末行:DEPRECATE_RESULT [...]
|
|
22
|
+
* 末行:DEPRECATE_RESULT [...] 或 DEPRECATE_CHECK_RESULT [...]
|
|
15
23
|
*/
|
|
16
24
|
|
|
17
25
|
const { execSync } = require('child_process');
|
|
@@ -44,6 +52,51 @@ function loadApps(wsRoot) {
|
|
|
44
52
|
}
|
|
45
53
|
}
|
|
46
54
|
|
|
55
|
+
/**
|
|
56
|
+
* 检查单个仓库(--check,只读):
|
|
57
|
+
* unpushed: 0 = 已全部推送;>0 = 本地领先远端 N 个提交;'all' = 远端无该分支(提交全部未推送)
|
|
58
|
+
*/
|
|
59
|
+
function checkRepo(name, repo, branch) {
|
|
60
|
+
const rec = { name, status: 'skipped', remote_exists: false, unpushed: 0, detail: null };
|
|
61
|
+
if (!fs.existsSync(path.join(repo, '.git'))) {
|
|
62
|
+
rec.detail = 'no-repo';
|
|
63
|
+
return rec;
|
|
64
|
+
}
|
|
65
|
+
const remote = run('git remote', repo);
|
|
66
|
+
if (!remote.ok || !remote.out) {
|
|
67
|
+
rec.detail = 'no-remote';
|
|
68
|
+
return rec;
|
|
69
|
+
}
|
|
70
|
+
const r = remote.out.split(/\r?\n/)[0];
|
|
71
|
+
if (!run('git rev-parse --verify --quiet "refs/heads/' + branch + '"', repo).ok) {
|
|
72
|
+
rec.detail = 'no-local-branch';
|
|
73
|
+
return rec;
|
|
74
|
+
}
|
|
75
|
+
const ls = run('git ls-remote --heads "' + r + '" "refs/heads/' + branch + '"', repo);
|
|
76
|
+
if (!ls.ok) {
|
|
77
|
+
rec.status = 'failed';
|
|
78
|
+
rec.detail = 'ls-remote-failed';
|
|
79
|
+
return rec;
|
|
80
|
+
}
|
|
81
|
+
if (!ls.out) {
|
|
82
|
+
rec.status = 'ok';
|
|
83
|
+
rec.unpushed = 'all';
|
|
84
|
+
rec.detail = 'remote-has-no-branch';
|
|
85
|
+
return rec;
|
|
86
|
+
}
|
|
87
|
+
const remoteHash = ls.out.split(/\r?\n/)[0].split(/\s+/)[0];
|
|
88
|
+
const cnt = run('git rev-list --count "' + remoteHash + '..refs/heads/' + branch + '"', repo);
|
|
89
|
+
if (!cnt.ok) {
|
|
90
|
+
rec.status = 'failed';
|
|
91
|
+
rec.detail = 'rev-list-failed';
|
|
92
|
+
return rec;
|
|
93
|
+
}
|
|
94
|
+
rec.status = 'ok';
|
|
95
|
+
rec.remote_exists = true;
|
|
96
|
+
rec.unpushed = parseInt(cnt.out, 10) || 0;
|
|
97
|
+
return rec;
|
|
98
|
+
}
|
|
99
|
+
|
|
47
100
|
/** 对单个仓库执行废弃重命名 */
|
|
48
101
|
function deprecateRepo(name, repo, branch) {
|
|
49
102
|
const newName = branch + '-deprecated';
|
|
@@ -95,6 +148,7 @@ function main() {
|
|
|
95
148
|
const appFilter = appsIdx >= 0
|
|
96
149
|
? new Set((args[appsIdx + 1] || '').split(',').map(s => s.trim()).filter(Boolean))
|
|
97
150
|
: null;
|
|
151
|
+
const checkOnly = args.includes('--check');
|
|
98
152
|
|
|
99
153
|
const repos = [{ name: 'workspace', repo: wsRoot }];
|
|
100
154
|
for (const app of loadApps(wsRoot)) {
|
|
@@ -102,6 +156,17 @@ function main() {
|
|
|
102
156
|
repos.push({ name: app.name, repo: path.join(wsRoot, '.codespace', app.name) });
|
|
103
157
|
}
|
|
104
158
|
|
|
159
|
+
if (checkOnly) {
|
|
160
|
+
const results = repos.map(rp => checkRepo(rp.name, rp.repo, branch));
|
|
161
|
+
results.forEach(r => {
|
|
162
|
+
const up = r.status === 'ok' && r.unpushed !== 0 ? ' [unpushed: ' + r.unpushed + ']' : '';
|
|
163
|
+
console.log(r.status.toUpperCase() + ' ' + r.name + (r.detail ? ' — ' + r.detail : '') + up);
|
|
164
|
+
});
|
|
165
|
+
console.log('---');
|
|
166
|
+
console.log('DEPRECATE_CHECK_RESULT', JSON.stringify({ branch, results }));
|
|
167
|
+
process.exit(results.some(r => r.status === 'failed') ? 2 : 0);
|
|
168
|
+
}
|
|
169
|
+
|
|
105
170
|
const results = repos.map(rp => deprecateRepo(rp.name, rp.repo, branch));
|
|
106
171
|
|
|
107
172
|
results.forEach(r => {
|
|
@@ -116,4 +181,4 @@ function main() {
|
|
|
116
181
|
|
|
117
182
|
if (require.main === module) main();
|
|
118
183
|
|
|
119
|
-
module.exports = { run, loadApps, deprecateRepo };
|
|
184
|
+
module.exports = { run, loadApps, checkRepo, deprecateRepo };
|