@ohos-cpf/3rdloop 0.0.1 → 0.0.2
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/README.md +160 -340
- package/lib/cli.js +43 -2
- package/lib/config-cmd.js +452 -0
- package/lib/config.js +13 -12
- package/lib/queries.js +1 -1
- package/lib/update.js +280 -0
- package/lib/workflow.js +19 -1
- package/package.json +1 -1
- package/vendor/Server/Brain/SkillExecutor.js +2 -2
- package/vendor/Server/CLI/cli.js +40 -8
- package/vendor/Server/CLI/deveco-code/index.js +71 -0
- package/vendor/Server/CLI/deveco-code.js +5 -0
- package/vendor/Server/CLI/opencode/index.js +16 -2
- package/vendor/Server/FlexRunner/FlexRunner.js +2 -2
- package/vendor/Server/Knowledge/KnowledgeImporter.js +1 -1
- package/vendor/Server/LoopEngine/LoopEngine.js +1 -1
- package/vendor/Server/Routes/server.js +5 -4
- package/vendor/Server/Skills/acceptance-check/SKILL.md +18 -1
- package/vendor/Server/Skills/acceptance-check/references/PHASE2_EVIDENCE_VERIFICATION.md +70 -0
- package/vendor/Server/Skills/arkts-code-check/SKILL.md +9 -2
- package/vendor/Server/Skills/arkts-code-check/references/CODE_CHECK_GUIDE.md +51 -0
- package/vendor/Server/Skills/arkts-code-check/references/PHASE1_INFO_COLLECTION.md +13 -2
- package/vendor/Server/TestCheck/TestCheck.js +2 -2
- package/vendor/VERSION +3 -3
- package/workflows/pr-push.mjs +193 -0
|
@@ -5,7 +5,7 @@ license: Apache-2.0
|
|
|
5
5
|
compatibility: 需要访问任务工作目录、产物文件
|
|
6
6
|
metadata:
|
|
7
7
|
author: LoopEngine
|
|
8
|
-
version: "1.0"
|
|
8
|
+
version: "1.1.0"
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# acceptance-check SKILL — 任务准出审核
|
|
@@ -84,6 +84,18 @@ Phase 1: 产物收集 → Phase 2: 逐条标准评估 → Phase 3: 准出结论
|
|
|
84
84
|
|
|
85
85
|
> 详细评估规则参见 [验收标准评估指南](references/PHASE2_CRITERIA_EVALUATION.md)
|
|
86
86
|
|
|
87
|
+
### 2.3 关键声明核验规则
|
|
88
|
+
|
|
89
|
+
对关键声明(分支血缘、产物存在、依赖版本、测试结果)执行核验时,必须遵守:
|
|
90
|
+
|
|
91
|
+
1. **证据三角验证**:验收判定禁止仅凭前序步骤报告的"成功"结论,关键声明必须与实物产物(文件存在性/大小/时间戳)、现场 git/文件查询(如 `git merge-base` 验证分支血缘、直接读依赖清单原文)交叉验证;
|
|
92
|
+
2. **依赖版本核验**:先读版本分析报告确认包架构(re-export / 源内复制 / fork),re-export 架构下区分适配包自身 `version` 字段与 `dependencies` 中的上游版本(二者语义不同);依赖残留区分框架/运行时依赖(阻断项)与 dev 工具依赖滞后(记录项),未阻断项必须如实写入报告"遗留记录"节;
|
|
93
|
+
3. **汇总指标分解**:验收标准含汇总数字(如 154/154、95/95)时,必须分解至报告分项逐项对账(如 59+85+10),防止口径错配;
|
|
94
|
+
4. **扫描执行安全**:核验类扫描(版本残留/引用检查)必须排除 node_modules/oh_modules/lock 文件/*.har/*.hap/.git,限定依赖清单文件白名单,优先使用 rg(grep 全量扫描在大仓库实测 120s 超时);
|
|
95
|
+
5. **自动生成产物权威级**:链路中自动生成的 diff/扫描件若在后续人工分析报告中被修正,验收一律以修正结论件为准,自动件仅作参考。
|
|
96
|
+
|
|
97
|
+
> 详细核验检查单、判定维度与示例参见 [关键声明核验规则指南](references/PHASE2_EVIDENCE_VERIFICATION.md)
|
|
98
|
+
|
|
87
99
|
---
|
|
88
100
|
|
|
89
101
|
## Phase 3:准出结论
|
|
@@ -144,6 +156,10 @@ Phase 1: 产物收集 → Phase 2: 逐条标准评估 → Phase 3: 准出结论
|
|
|
144
156
|
(仅在未通过时提供)
|
|
145
157
|
1. {改进建议1}
|
|
146
158
|
2. {改进建议2}
|
|
159
|
+
|
|
160
|
+
## 遗留记录
|
|
161
|
+
(如有:核验中发现的非阻断遗留项)
|
|
162
|
+
- {遗留项描述 + 风险定性 + 处置建议}
|
|
147
163
|
```
|
|
148
164
|
|
|
149
165
|
### 4.2 审核摘要格式
|
|
@@ -172,3 +188,4 @@ Phase 1: 产物收集 → Phase 2: 逐条标准评估 → Phase 3: 准出结论
|
|
|
172
188
|
2. 审核报告必须逐条列出每个验收标准的评估结论
|
|
173
189
|
3. 未通过时必须提供改进建议
|
|
174
190
|
4. 报告和摘要文件必须写入指定路径
|
|
191
|
+
5. 核验中发现的非阻断遗留项必须如实记录在报告"遗留记录"节(含风险定性),不得隐藏不报,也不得因非阻断遗留项直接判未通过
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# 关键声明核验规则指南(Phase 2.3 扩展)
|
|
2
|
+
|
|
3
|
+
> 适用于验收标准中涉及关键声明(分支血缘、产物存在、依赖版本、测试结果)的核验场景,尤其是"loop N 复用 loop N-1 产物"的跨循环核验任务。
|
|
4
|
+
|
|
5
|
+
## 1. 证据三角验证
|
|
6
|
+
|
|
7
|
+
验收判定禁止仅凭前序步骤报告的"成功"结论。每类关键声明的交叉验证方式:
|
|
8
|
+
|
|
9
|
+
| 声明类型 | 报告声明(第一角) | 实物产物(第二角) | 现场查询(第三角) |
|
|
10
|
+
|----------|--------------------|--------------------|--------------------|
|
|
11
|
+
| 分支血缘 | 前序报告的分支/commit 描述 | — | `git merge-base <升级分支> <基线分支>` 实测 |
|
|
12
|
+
| 编译产物 | 编译记录的"成功"结论 | 文件存在性 + 大小 + 时间戳(HAR/tgz/HAP 实体文件) | — |
|
|
13
|
+
| 依赖版本 | 版本分析报告结论 | — | 直接读依赖清单文件原文(package.json / oh-package.json5) |
|
|
14
|
+
| 测试产物 | 测试记录的通过率 | 测试目录/文件实际清单 | 列目录与报告清单一一比对 |
|
|
15
|
+
|
|
16
|
+
## 2. 依赖版本核验检查单
|
|
17
|
+
|
|
18
|
+
### 2.1 版本字段定位(先懂架构再核字段)
|
|
19
|
+
|
|
20
|
+
核验依赖版本前,先读版本分析报告确认包架构:
|
|
21
|
+
|
|
22
|
+
| 包架构 | 上游版本所在位置 | 适配包自身版本 |
|
|
23
|
+
|--------|------------------|----------------|
|
|
24
|
+
| re-export(JS 层仅 re-export 上游 npm 包) | `dependencies` 中的上游源库条目 | `version` 字段(历史沿袭,与 tgz 文件名对应,可能与上游版本不同步) |
|
|
25
|
+
| 源内复制 / fork | `version` 字段或源码内版本标注 | 同左 |
|
|
26
|
+
|
|
27
|
+
检查项:
|
|
28
|
+
- [ ] 同时检查 `version`(适配包自身)与 `dependencies`(上游源库)两个字段
|
|
29
|
+
- [ ] 与交付物 tgz 文件名交叉确认适配包自身版本
|
|
30
|
+
- [ ] 在核验记录中显式写明版本字段语义说明,避免后续循环按字面直读 `version` 误判
|
|
31
|
+
|
|
32
|
+
### 2.2 依赖残留分层判定(二分法)
|
|
33
|
+
|
|
34
|
+
| 判定维度 | 阻断项(核验失败) | 记录项(非阻断) |
|
|
35
|
+
|----------|--------------------|------------------|
|
|
36
|
+
| 属于 RNOH 框架依赖(react-native / react-native-harmony / @rnoh/react-native-openharmony / nitro-modules / harmony-cli) | 是 | 否 |
|
|
37
|
+
| 参与编译链(HAR→tgz→codegen→JS Bundle→ohpm→devecocli build) | 是 | 否 |
|
|
38
|
+
| 参与运行时 | 是 | 否 |
|
|
39
|
+
|
|
40
|
+
记录项(如 ESLint 配置等代码风格工具滞后)定性需三对照:上游基线版本(diff-output 中上游 package.json)+ example 侧同款版本 + 编译链涉及范围。
|
|
41
|
+
|
|
42
|
+
处置:如实写入报告"遗留记录"节 + 给出风险定性(如低风险)+ 不阻断验收。隐藏不报或直接判失败都是错误的。
|
|
43
|
+
|
|
44
|
+
## 3. 汇总指标分解
|
|
45
|
+
|
|
46
|
+
验收标准含汇总数字时,必须分解至报告分项逐项对账:
|
|
47
|
+
|
|
48
|
+
- 示例:"设备端 95/95" = 自动套件 85/85 + Hooks 探针 10/10;"共 154/154" = Jest 59 + 85 + 10
|
|
49
|
+
- 判定口径一致的条件:分项之和 = 汇总数,且各分项均能在报告中找到直接对应的数字
|
|
50
|
+
- 只对总数不分解,会漏掉口径错配
|
|
51
|
+
|
|
52
|
+
## 4. 扫描执行安全指引
|
|
53
|
+
|
|
54
|
+
核验类扫描(版本残留/引用检查)的执行约束:
|
|
55
|
+
|
|
56
|
+
- 优先使用 `rg`,禁止对含 node_modules 的仓库做 grep 全量扫描(实测 120s 超时被强制终止)
|
|
57
|
+
- 必须叠加排除参数:`-g '!node_modules' -g '!oh_modules' -g '!package-lock.json' -g '!*.har' -g '!*.hap' -g '!\.git'`
|
|
58
|
+
- 只扫依赖清单白名单文件:根/example 的 package.json、oh-package.json5、nitro.json、overrides/resolutions
|
|
59
|
+
- 只匹配框架相关包名的版本号模式,避免误报
|
|
60
|
+
|
|
61
|
+
## 5. 自动生成产物权威级
|
|
62
|
+
|
|
63
|
+
链路中自动生成的 diff/扫描件(如 rn-scan-exports.cjs 生成的 api-diff_*.md)存在已知偏差:
|
|
64
|
+
|
|
65
|
+
- Nitro HybridObject 架构的库,Spec 文件为 `*.nitro.ts` 而非 `Native*.ts` 命名,扫描器规则不覆盖,可能误判"TurboModule Spec 变更:无"
|
|
66
|
+
- "JS 导出变更:无"等结论可能为误报
|
|
67
|
+
|
|
68
|
+
判定规则:
|
|
69
|
+
- 自动生成件若在后续人工分析报告中被修正 → 验收一律以修正结论件为准,自动件仅作参考
|
|
70
|
+
- 核验前先确认产物链中每个文件的权威级(自动生成件 / 人工修正结论件),否则会拿错误基线对账
|
|
@@ -4,7 +4,7 @@ description: 自动化检视 ArkTS 三方库 Pull Request,包括获取 PR 差
|
|
|
4
4
|
license: Apache-2.0
|
|
5
5
|
compatibility: 需要 git、curl、网络访问代码仓库平台 API
|
|
6
6
|
metadata:
|
|
7
|
-
version: "1.
|
|
7
|
+
version: "1.1.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# ArkTsCodeCheckSkill — ArkTS 三方库 PR 代码检视
|
|
@@ -126,7 +126,10 @@ git diff {targetBranch}..pr{prNumber} -- {targetLibraryDir}/ > {outputDir}/pr-di
|
|
|
126
126
|
git diff --stat {targetBranch}..pr{prNumber} -- {targetLibraryDir}/
|
|
127
127
|
|
|
128
128
|
# 重新生成 changed-files.txt(基于过滤后的 diff)
|
|
129
|
-
|
|
129
|
+
# ⚠️ 禁止 sed 's/diff --git a\/.* b\/\//'(macOS BSD sed 不兼容会静默生成空文件),改用 awk 提取
|
|
130
|
+
grep "^diff --git" {outputDir}/pr-diff.txt | awk '{print $4}' | sed 's/^b\///' > {outputDir}/changed-files.txt
|
|
131
|
+
# 生成后必须校验非空(空文件 = 提取失败,禁止继续后续流程)
|
|
132
|
+
wc -l {outputDir}/changed-files.txt
|
|
130
133
|
```
|
|
131
134
|
|
|
132
135
|
> **原则**:检视应聚焦于 PR 的意图范围(单次提交涉及的目标库),不应将 fork 分支 divergent master 上的其他变更纳入检视和分级。
|
|
@@ -177,6 +180,10 @@ grep "^diff --git" {outputDir}/pr-diff.txt | sed 's/diff --git a\/.* b\/\//' > {
|
|
|
177
180
|
|
|
178
181
|
> 完整检查清单参见 [代码检视指南](references/CODE_CHECK_GUIDE.md)
|
|
179
182
|
|
|
183
|
+
**专项检查规范**(详见指南 3.14 / 3.15 节):
|
|
184
|
+
- **静态检查工具(arkts_check)使用规范**:必须以绝对路径调用(相对路径会产生假阴性静默漏检);`Cannot find module '@ohos/*'` 为 ohpm 本地依赖解析的环境误报,不计入问题统计;采用**基线对照法**(对 master 既有文件跑同样检查)区分"PR 引入"与"历史存量"违规
|
|
185
|
+
- **测试型 PR 检查清单**:纯测试补充型 PR 须执行导入符号核验、构造函数签名比对、夹具数据解码核验、未使用导入检测、新文件 License 头一致性、测试脚手架与库内部契约链路确认
|
|
186
|
+
|
|
180
187
|
### 3.2 问题严重程度
|
|
181
188
|
|
|
182
189
|
| 级别 | 标识 | 说明 |
|
|
@@ -151,6 +151,57 @@ for (let i = 0; i < services.length; i++) {
|
|
|
151
151
|
|
|
152
152
|
---
|
|
153
153
|
|
|
154
|
+
## 3.14 静态检查工具使用规范(arkts_check)
|
|
155
|
+
|
|
156
|
+
> ⚠️ 使用独立 arkts_check 工具对 PR 文件做静态检查时必须遵守以下规范,避免假阴性与误报污染检视结论。
|
|
157
|
+
|
|
158
|
+
### 3.14.1 必须使用绝对路径调用
|
|
159
|
+
|
|
160
|
+
- 以**相对路径**(如 `entry/src/ohosTest/...`)批量调用 arkts_check 会产生**假阴性**:工具返回 "No errors found",但改用绝对路径复查同一文件立即报出真实错误(含 `arkts-no-any-unknown` 违规)
|
|
161
|
+
- 相对路径批量调用静默漏检,若直接采信会得出"零静态检查问题"的错误结论,直接影响合入分级
|
|
162
|
+
- 必要时用一个"已知有问题的文件"做**阳性对照**,验证工具确实在检查
|
|
163
|
+
|
|
164
|
+
### 3.14.2 环境误报甄别(不计入问题统计)
|
|
165
|
+
|
|
166
|
+
- `Cannot find module '@ohos/*'` 类错误:独立 linter 无法解析 ohpm 本地依赖(`@ohos/xxx` → `file:../library`)产生的环境限制误报,**不计入问题统计**
|
|
167
|
+
- 批量调用可能实际检查目录内全部文件(含 master 既有文件),输出混入非目标文件错误;需对工具保存的完整输出**按目标文件名 grep 过滤**后再统计,或单文件逐个检查(更可控)
|
|
168
|
+
|
|
169
|
+
### 3.14.3 基线对照法(区分 PR 引入 vs 历史存量)
|
|
170
|
+
|
|
171
|
+
对 master 既有文件(本 PR 未修改的文件)运行同样的静态检查作为**基线**:
|
|
172
|
+
|
|
173
|
+
- 既有文件的违规基数 = 历史存量,与本 PR 无关
|
|
174
|
+
- PR 新增文件的报错需与基线对照后,仅将**超出基线的新增违规**计入 PR 问题
|
|
175
|
+
- 实际案例:mp3agic PR #53 中 master 既有测试文件基线为 125+ 错误(大量历史存量 `arkts-no-any-unknown`),而 PR 新增代码仅 1 处真实违规,显著优于仓库基线
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## 3.15 测试型 PR 检查清单
|
|
180
|
+
|
|
181
|
+
> 纯测试补充型 PR(不修改库源码)的检视核心是**验证测试本身正确**。此类 PR 的问题通常集中在合规与代码卫生而非功能,分级时应避免因"零功能风险"直接放行 PASS。
|
|
182
|
+
|
|
183
|
+
### 三层交叉验证
|
|
184
|
+
|
|
185
|
+
1. **导入符号核验**
|
|
186
|
+
- 深路径导入(`@ohos/xxx/src/main/ets/...`)逐一确认目标文件存在
|
|
187
|
+
- 根入口导入确认符号在 `library/index.ets` 导出面中
|
|
188
|
+
- 深路径导入若为仓库既有模式(grep 既有测试确认),不作为问题上报
|
|
189
|
+
|
|
190
|
+
2. **构造函数签名比对**
|
|
191
|
+
- 测试调用与库源码构造签名逐一比对(参数数量、类型、顺序)
|
|
192
|
+
|
|
193
|
+
3. **夹具数据解码核验**
|
|
194
|
+
- 对 base64 内嵌夹具本地解码(python3 + base64),核验文件头/长度/关键 offset/关键字节与测试断言值吻合
|
|
195
|
+
|
|
196
|
+
### 配套检查项
|
|
197
|
+
|
|
198
|
+
- [ ] **测试脚手架与库内部契约链路**:测试 setup 依赖的库未文档化行为(如 `GlobalContext` 键名的写入/读取)必须到库源码中确认精确对应,否则测试在其他环境可能静默失败
|
|
199
|
+
- [ ] **未使用导入检测**:对测试文件按 import 符号 grep 计数(出现 ≤1 次即未使用)
|
|
200
|
+
- [ ] **新文件 License 头一致性**:对照仓库既有测试文件的头格式,缺失即报合规问题(OAT 扫描风险)——测试型 PR 极易遗漏此项
|
|
201
|
+
- [ ] **测试文件头注释与实现一致性**:夹具机制注释与实际实现不符会误导维护者,应抽查(如注释写"从 rawfile 复制"而实现是 base64 内嵌解码)
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
154
205
|
## 问题严重程度分级
|
|
155
206
|
|
|
156
207
|
| 级别 | 标识 | 说明 |
|
|
@@ -190,7 +190,12 @@ git diff {targetBranch}..pr{prNumber} -- {targetLibraryDir}/ > {outputDir}/pr-di
|
|
|
190
190
|
git diff --stat {targetBranch}..pr{prNumber} -- {targetLibraryDir}/
|
|
191
191
|
|
|
192
192
|
# 重新生成 changed-files.txt(基于过滤后的 diff)
|
|
193
|
-
|
|
193
|
+
# ⚠️ 禁止使用 sed 's/diff --git a\/.* b\/\//' —— macOS BSD sed 对该转义组合不兼容,
|
|
194
|
+
# 会报 "unterminated substitute in regular expression" 并静默生成 0 字节空文件
|
|
195
|
+
grep "^diff --git" {outputDir}/pr-diff.txt | awk '{print $4}' | sed 's/^b\///' > {outputDir}/changed-files.txt
|
|
196
|
+
|
|
197
|
+
# 生成后必须校验非空(0 字节空文件 = 提取失败,会导致后续检视范围判断失据,且收尾前不易察觉)
|
|
198
|
+
wc -l {outputDir}/changed-files.txt
|
|
194
199
|
```
|
|
195
200
|
|
|
196
201
|
### 边界情况
|
|
@@ -216,7 +221,13 @@ grep "^diff --git" {outputDir}/pr-diff.txt | sed 's/diff --git a\/.* b\/\//' > {
|
|
|
216
221
|
**生成 changed-files.txt**:
|
|
217
222
|
```bash
|
|
218
223
|
# 基于范围过滤后的 diff 生成(而非 pr-diff-raw.txt 或 pr.patch)
|
|
219
|
-
|
|
224
|
+
# ⚠️ 禁止使用 sed 's/diff --git a\/.* b\/\//' —— macOS BSD sed 不兼容该转义组合,
|
|
225
|
+
# 会静默生成 0 字节空文件;改用 awk 提取第 4 列再去掉 b/ 前缀
|
|
226
|
+
grep "^diff --git" {outputDir}/pr-diff.txt | awk '{print $4}' | sed 's/^b\///' > {outputDir}/changed-files.txt
|
|
227
|
+
|
|
228
|
+
# 生成后必须校验非空(自检步骤:空文件意味着提取失败,必须立即发现而非收尾时)
|
|
229
|
+
wc -l {outputDir}/changed-files.txt
|
|
220
230
|
```
|
|
221
231
|
|
|
222
232
|
> **注意**:changed-files.txt 必须基于 `pr-diff.txt`(范围过滤后的累积 diff)生成,而非 `pr-diff-raw.txt`(全量累积 diff)或 `pr.patch`(单次 commit)。
|
|
233
|
+
> **自检要求**:`wc -l` 结果为 0 行时禁止继续后续流程,必须先排查 diff 生成与提取命令是否失败(实际案例:mp3agic PR #53 中 BSD sed 静默失败生成空文件,直至收尾才发现,险些遗漏检视范围)。
|
|
@@ -140,7 +140,7 @@ class TestCheck {
|
|
|
140
140
|
async _getCli() {
|
|
141
141
|
if (!this.cli) {
|
|
142
142
|
const CLIController = await _loadCLIController();
|
|
143
|
-
this.cli = new CLIController(
|
|
143
|
+
this.cli = new CLIController();
|
|
144
144
|
}
|
|
145
145
|
return this.cli;
|
|
146
146
|
}
|
|
@@ -417,7 +417,7 @@ class TestCheck {
|
|
|
417
417
|
// 健康检查
|
|
418
418
|
const health = await cli.checkHealth();
|
|
419
419
|
if (health.status !== 'ok') {
|
|
420
|
-
this._log('error', `
|
|
420
|
+
this._log('error', `CLI(${cli.getType()}) 服务不可用: ${health.message}`);
|
|
421
421
|
return { sessionId: '', executed: false };
|
|
422
422
|
}
|
|
423
423
|
|
package/vendor/VERSION
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
0.0.
|
|
2
|
-
built=2026-09-
|
|
3
|
-
sha=
|
|
1
|
+
0.0.2
|
|
2
|
+
built=2026-09-02T09:38:34.082Z
|
|
3
|
+
sha=a178b8f80998c827
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pr-push.mjs —— 三方库自动提 PR 工作流
|
|
3
|
+
*
|
|
4
|
+
* 绑定 ohos-lib-pr-push SKILL(Server/Skills/ohos-lib-pr-push)的单步骤固定编排:
|
|
5
|
+
* 总结修改点 → 源仓库建 Issue →(条件)更新 CHANGELOG → 提交推送(fork)→
|
|
6
|
+
* 从 fork 向源仓库建 PR → 评论 Start build 触发 CI → 生成报告
|
|
7
|
+
*
|
|
8
|
+
* 与 pr-check.mjs 的差异:
|
|
9
|
+
* - 发布操作非幂等(重试可能重复建 Issue/PR),maxRetries 固定为 0(恰好执行一次)
|
|
10
|
+
* - workspace-dir 缺省时默认取 CLI 命令执行的目录(process.cwd())
|
|
11
|
+
* - 编排成功后通过 afterRun 钩子把最终报告复制到 CLI 执行目录
|
|
12
|
+
*
|
|
13
|
+
* 注册约定见 cli/README_DEVELOP.md「工作流机制」章节:默认导出声明对象,自动注册。
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import fs from 'node:fs';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import { execSync } from 'node:child_process';
|
|
19
|
+
import crypto from 'node:crypto';
|
|
20
|
+
|
|
21
|
+
const SKILL_NAME = 'ohos-lib-pr-push';
|
|
22
|
+
|
|
23
|
+
/** 时间戳(base36,与 pr-check.mjs 的 ts36 一致)。 */
|
|
24
|
+
function ts36() {
|
|
25
|
+
return Date.now().toString(36);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** 生成 prpush_ 前缀的 taskId(随机位 8,避免并发碰撞)。 */
|
|
29
|
+
function makeTaskId() {
|
|
30
|
+
let rand = '';
|
|
31
|
+
try {
|
|
32
|
+
const bytes = crypto.randomBytes(6);
|
|
33
|
+
rand = bytes.toString('base64url').replace(/[^a-z0-9]/gi, '').slice(0, 8).toLowerCase();
|
|
34
|
+
} catch {
|
|
35
|
+
rand = Math.random().toString(36).substring(2, 10);
|
|
36
|
+
}
|
|
37
|
+
if (rand.length < 8) {
|
|
38
|
+
rand = (rand + Math.random().toString(36).substring(2, 10)).slice(0, 8);
|
|
39
|
+
}
|
|
40
|
+
return `prpush_${ts36()}${rand}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 校验目录为 git 仓库根(且目录存在)。
|
|
45
|
+
* 失败时 throw(框架捕获 → 退出码 1),不触碰引擎。
|
|
46
|
+
*/
|
|
47
|
+
function assertGitRepo(dir) {
|
|
48
|
+
if (!dir || !fs.existsSync(dir)) {
|
|
49
|
+
throw new Error(`workspace-dir 目录不存在: ${dir || '(空)'}`);
|
|
50
|
+
}
|
|
51
|
+
const stat = fs.statSync(dir);
|
|
52
|
+
if (!stat.isDirectory()) {
|
|
53
|
+
throw new Error(`workspace-dir 不是目录: ${dir}`);
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
const topLevel = execSync('git rev-parse --show-toplevel', {
|
|
57
|
+
cwd: dir,
|
|
58
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
59
|
+
encoding: 'utf8',
|
|
60
|
+
}).trim();
|
|
61
|
+
if (!topLevel) throw new Error('git rev-parse 返回空');
|
|
62
|
+
} catch {
|
|
63
|
+
throw new Error(`workspace-dir 不是 git 仓库: ${dir}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** 目标目录下已存在同名报告 → 改名备份 .bak_<ts>,保证固定文件名。 */
|
|
68
|
+
function backupIfExists(targetReportPath) {
|
|
69
|
+
if (!fs.existsSync(targetReportPath)) return;
|
|
70
|
+
const bak = `${targetReportPath}.bak_${Date.now()}`;
|
|
71
|
+
fs.renameSync(targetReportPath, bak);
|
|
72
|
+
process.stderr.write(`[pr-push] 已备份旧报告: ${bak}\n`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 把 `{workspaceDir}/ohos-lib-pr-push-report.md` 复制到 CLI 执行目录。
|
|
77
|
+
* 源不存在 → 提示但不抛错(AI 可能未生成,不阻断退出码)。
|
|
78
|
+
*/
|
|
79
|
+
function copyReportToCwd(workspaceDir, cwd) {
|
|
80
|
+
const src = path.join(workspaceDir, 'ohos-lib-pr-push-report.md');
|
|
81
|
+
if (!fs.existsSync(src)) {
|
|
82
|
+
process.stderr.write(`[pr-push] 未找到报告文件(跳过复制): ${src}\n`);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const target = path.join(cwd, 'ohos-lib-pr-push-report.md');
|
|
86
|
+
backupIfExists(target);
|
|
87
|
+
fs.copyFileSync(src, target);
|
|
88
|
+
process.stderr.write(`[pr-push] 报告已复制到: ${target}\n`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export default {
|
|
92
|
+
name: 'pr-push',
|
|
93
|
+
description: '三方库自动提 PR:fork 工作区修改 → 源仓库 Issue/PR/CHANGELOG + 触发 CI',
|
|
94
|
+
params: [
|
|
95
|
+
{ flag: 'workspace-dir', required: false, describe: '本地 fork 仓库克隆的 git 根目录(缺省默认取 CLI 执行目录)' },
|
|
96
|
+
{ flag: 'upstream-url', required: false, describe: '源仓库(上游)URL,缺省按 SKILL Phase 1.1 推断' },
|
|
97
|
+
{ flag: 'base-branch', required: false, describe: 'PR 目标(上游)分支,缺省优先上游同名分支,否则上游默认分支' },
|
|
98
|
+
{ flag: 'branch-name', required: false, describe: '提交分支名(SKILL 默认用当前分支,不自动建/切分支)' },
|
|
99
|
+
{ flag: 'commit-message', required: false, describe: '提交信息,缺省由修改点总结自动生成' },
|
|
100
|
+
{ flag: 'changelog', required: false, describe: 'CHANGELOG 判定:auto(默认)/true/false' },
|
|
101
|
+
{ flag: 'dry-run', required: false, describe: 'SKILL 级 dry-run:true 时只总结修改点与生成报告,不执行发布(默认 false)' },
|
|
102
|
+
],
|
|
103
|
+
buildTaskDef(params, env) {
|
|
104
|
+
// ── workspace-dir 判定:显式传入优先,否则默认 CLI 执行目录 ──
|
|
105
|
+
const rawWs = String(params['workspace-dir'] || '').trim();
|
|
106
|
+
const workspaceDir = rawWs ? path.resolve(rawWs) : path.resolve(env.cwd || process.cwd());
|
|
107
|
+
assertGitRepo(workspaceDir);
|
|
108
|
+
|
|
109
|
+
const upstreamUrl = String(params['upstream-url'] || '').trim();
|
|
110
|
+
const baseBranch = String(params['base-branch'] || '').trim();
|
|
111
|
+
const branchName = String(params['branch-name'] || '').trim();
|
|
112
|
+
const commitMessage = String(params['commit-message'] || '').trim();
|
|
113
|
+
const changelog = String(params['changelog'] || '').trim().toLowerCase();
|
|
114
|
+
if (changelog && !['auto', 'true', 'false'].includes(changelog)) {
|
|
115
|
+
throw new Error(`changelog 取值非法: ${changelog}(可选: auto/true/false)`);
|
|
116
|
+
}
|
|
117
|
+
const dryRunRaw = String(params['dry-run'] || '').trim().toLowerCase();
|
|
118
|
+
if (dryRunRaw && !['true', 'false'].includes(dryRunRaw)) {
|
|
119
|
+
throw new Error(`dry-run 取值非法: ${dryRunRaw}(可选: true/false)`);
|
|
120
|
+
}
|
|
121
|
+
const dryRun = dryRunRaw === 'true';
|
|
122
|
+
|
|
123
|
+
const taskId = makeTaskId();
|
|
124
|
+
const titleBase = path.basename(workspaceDir);
|
|
125
|
+
const title = `PR推送|${titleBase}|${baseBranch || 'default'}`;
|
|
126
|
+
|
|
127
|
+
// ── taskDescription:把参数显式写明,供 AI 执行 SKILL Phase 1-8 ──
|
|
128
|
+
const paramLines = [
|
|
129
|
+
`workspaceDir: ${workspaceDir}`,
|
|
130
|
+
`upstreamUrl: ${upstreamUrl || '(未提供,按 SKILL Phase 1.1 推断)'}`,
|
|
131
|
+
`baseBranch: ${baseBranch || '(未提供,按 SKILL Phase 1.5 优先上游同名分支,否则上游默认分支)'}`,
|
|
132
|
+
`branchName: ${branchName || '(未提供,默认使用当前分支,禁止创建/切换分支)'}`,
|
|
133
|
+
`commitMessage: ${commitMessage || '(未提供,由修改点总结自动生成)'}`,
|
|
134
|
+
`changelog: ${changelog || 'auto(按 SKILL Phase 2.4 判定是否更新 CHANGELOG)'}`,
|
|
135
|
+
`dryRun: ${dryRun}`,
|
|
136
|
+
];
|
|
137
|
+
const taskDescription = [
|
|
138
|
+
'请执行 ohos-lib-pr-push SKILL 的三方库自动提 PR 流程。',
|
|
139
|
+
'',
|
|
140
|
+
'## 任务参数',
|
|
141
|
+
...paramLines,
|
|
142
|
+
'',
|
|
143
|
+
'## 执行要求',
|
|
144
|
+
'1. 严格按照 SKILL.md 中 Phase 1-8 的顺序执行,顺序不可颠倒。',
|
|
145
|
+
'2. Token 从环境变量 GIT_TOKEN 或 Server/.env 读取,绝不写入日志或报告。',
|
|
146
|
+
'3. 只发布不修改:发现代码问题应停下报告,不顺手改代码。',
|
|
147
|
+
'4. 禁止创建或切换分支:当前在哪个分支就在该分支提交并推送,PR head 即该分支。',
|
|
148
|
+
'5. 若 dryRun=true,仅执行 Phase 1/2/8(确定工作区、总结修改点、生成报告),跳过 Issue/CHANGELOG/提交推送/PR/CI。',
|
|
149
|
+
'',
|
|
150
|
+
'## 产物约束',
|
|
151
|
+
'1. 最终报告必须写入 `{workspaceDir}/ohos-lib-pr-push-report.md`(跳过报告导致后续无法复制,务必生成)。',
|
|
152
|
+
'2. 执行完成后,把本次执行结果(`## 执行状态` 字段,值为 `成功` 或 `失败`)写入下述"结果文件输出路径"所指文件。',
|
|
153
|
+
' - 若流程成功(或失败但已按 SKILL 失败恢复逻辑产出报告),在结果文件中写 `## 执行状态: 成功`;',
|
|
154
|
+
' - 若整体失败且无法生成报告,写 `## 执行状态: 失败`。',
|
|
155
|
+
].join('\n');
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
taskId,
|
|
159
|
+
title,
|
|
160
|
+
skillDirectory: '',
|
|
161
|
+
loopCount: 0,
|
|
162
|
+
steps: [{
|
|
163
|
+
stepId: 1,
|
|
164
|
+
taskDescription,
|
|
165
|
+
dependId: 0,
|
|
166
|
+
nextStepId: 0,
|
|
167
|
+
failStepId: 0,
|
|
168
|
+
maxRetries: 0, // 发布非幂等:恰好执行一次,绝不自动重试
|
|
169
|
+
skillDirectory: '',
|
|
170
|
+
specifiedSkill: SKILL_NAME,
|
|
171
|
+
inputReport: '',
|
|
172
|
+
}],
|
|
173
|
+
};
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* afterRun 钩子:编排完成后把 `{workspaceDir}` 下的最终报告复制到 CLI 执行目录。
|
|
178
|
+
* 仅在任务成功(result.passed)时复制;失败时保留工作区内报告供人工查看。
|
|
179
|
+
* 框架容错:本函数抛错只记录不改变退出码。
|
|
180
|
+
*/
|
|
181
|
+
async afterRun({ params, taskDef, result, dataDir, cwd }) {
|
|
182
|
+
if (!taskDef || !result || result.passed !== true) {
|
|
183
|
+
if (result && result.passed !== true) {
|
|
184
|
+
process.stderr.write(`[pr-push] 编排未通过(status=${result.status}),跳过报告复制(报告保留在工作区)\n`);
|
|
185
|
+
}
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
// 同一 parser 逻辑:显式传入优先,否则默认 CLI 执行目录
|
|
189
|
+
const rawWs = String(params['workspace-dir'] || '').trim();
|
|
190
|
+
const workspaceDir = rawWs ? path.resolve(rawWs) : path.resolve(cwd || process.cwd());
|
|
191
|
+
copyReportToCwd(workspaceDir, cwd || process.cwd());
|
|
192
|
+
},
|
|
193
|
+
};
|