@ohos-cpf/3rdloop 0.0.5 → 0.0.6
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/lib/cli.js +3 -3
- package/lib/serve.js +14 -2
- package/package.json +1 -1
- package/vendor/Server/CLI/opencode/index.js +2 -2
- package/vendor/Server/DbUse/StorageManager.js +7 -1
- package/vendor/Server/Routes/controllers/FlexRunnerController.js +92 -0
- package/vendor/Server/Routes/routes/flexrunner.js +2 -0
- package/vendor/VERSION +3 -3
- package/web/js/arktslibrarycheck.js +82 -0
- package/web/js/codecheck.js +59 -0
- package/web/js/flutterlibrarycheck.js +84 -1
- package/web/js/rnlibrarycheck.js +85 -1
package/lib/cli.js
CHANGED
|
@@ -145,7 +145,7 @@ export async function runCli(argv) {
|
|
|
145
145
|
return cmdConfig({ flags, rest, projectRoot, user3libHome, jsonMode, out });
|
|
146
146
|
|
|
147
147
|
case 'serve':
|
|
148
|
-
return cmdServe({ flags, rest, jsonMode });
|
|
148
|
+
return cmdServe({ flags, rest, jsonMode, dataDir });
|
|
149
149
|
|
|
150
150
|
case 'update':
|
|
151
151
|
return cmdUpdate({ flags, rest, jsonMode, out });
|
|
@@ -226,9 +226,9 @@ async function cmdUpdate({ flags, rest, jsonMode, out }) {
|
|
|
226
226
|
|
|
227
227
|
// ─── serve(启动 Server HTTP 后端,供前端展示)─────────────────────────
|
|
228
228
|
|
|
229
|
-
async function cmdServe({ flags, rest, jsonMode }) {
|
|
229
|
+
async function cmdServe({ flags, rest, jsonMode, dataDir }) {
|
|
230
230
|
const { cmdServe: impl } = await import('./serve.js');
|
|
231
|
-
return impl({ rest: flags.rest, jsonMode });
|
|
231
|
+
return impl({ rest: flags.rest, jsonMode, dataDir });
|
|
232
232
|
}
|
|
233
233
|
|
|
234
234
|
// ─── run / submit ──────────────────────────────────────────────────
|
package/lib/serve.js
CHANGED
|
@@ -88,9 +88,10 @@ function _parseServeArgs(rest = []) {
|
|
|
88
88
|
* @param {object} opts
|
|
89
89
|
* @param {string[]} opts.rest - flags.rest(含 serve 专用参数)
|
|
90
90
|
* @param {boolean} opts.jsonMode
|
|
91
|
+
* @param {string} [opts.dataDir] - CLI 解析后的数据目录(--data-dir / 3LIB_DATA_DIR / 默认)
|
|
91
92
|
* @returns {Promise<number>} 退出码(serve 为长驻命令,仅 --help/启动失败正常返回)
|
|
92
93
|
*/
|
|
93
|
-
export async function cmdServe({ rest = [], jsonMode = false }) {
|
|
94
|
+
export async function cmdServe({ rest = [], jsonMode = false, dataDir = '' }) {
|
|
94
95
|
// --help / -h:打印用法
|
|
95
96
|
if (rest.includes('--help') || rest.includes('-h')) {
|
|
96
97
|
_printServeHelp();
|
|
@@ -106,6 +107,15 @@ export async function cmdServe({ rest = [], jsonMode = false }) {
|
|
|
106
107
|
return 1;
|
|
107
108
|
}
|
|
108
109
|
|
|
110
|
+
// ── 数据目录对齐 CLI(A3):与 run/submit 共用同一 dataDir ──────
|
|
111
|
+
// 通过 env 注入而非逐层传参:Server 内部所有自建 createStorage()
|
|
112
|
+
// 都经 StorageManager 默认根目录解析,env 一处生效(含 FlexRunner /
|
|
113
|
+
// KnowledgeManager / MessageArchive 等惰性自建实例)。
|
|
114
|
+
// 必须在 import Server 模块前设置(覆盖部分模块加载期构造的实例)。
|
|
115
|
+
if (dataDir) {
|
|
116
|
+
process.env['3LIB_DATA_DIR'] = dataDir;
|
|
117
|
+
}
|
|
118
|
+
|
|
109
119
|
const serverModule = await _importServerModule();
|
|
110
120
|
|
|
111
121
|
// json 模式下纯化 stdout:Server 内部 console.log/info 重定向到 stderr
|
|
@@ -141,7 +151,9 @@ export async function cmdServe({ rest = [], jsonMode = false }) {
|
|
|
141
151
|
|
|
142
152
|
try {
|
|
143
153
|
// cliType 由环境决定(shell > .env > 默认 opencode),与 CLI 嵌入式一致;
|
|
144
|
-
// dataDir
|
|
154
|
+
// dataDir 已通过 3LIB_DATA_DIR env 注入(见上),Server 内部所有默认
|
|
155
|
+
// createStorage() 均落到用户数据目录;skillDir 沿用包内 vendor 默认
|
|
156
|
+
// (npm 更新自动获得新 SKILL,不与 CLI 的 3LIB_SKILL_DIR 联动)
|
|
145
157
|
const server = await serverModule.createServer({ port: serverPort });
|
|
146
158
|
|
|
147
159
|
if (jsonMode) {
|
package/package.json
CHANGED
|
@@ -388,7 +388,7 @@ class OpenCodeCLI {
|
|
|
388
388
|
try {
|
|
389
389
|
statusMap = await this.client.session.status({ query: {} });
|
|
390
390
|
} catch (e) {
|
|
391
|
-
this._log('
|
|
391
|
+
this._log('debug', `checkSessionStatus: session.status() 失败,降级为拉消息: ${e.message}`);
|
|
392
392
|
}
|
|
393
393
|
const rawStatus = statusMap?.data?.[sessionId];
|
|
394
394
|
if (rawStatus && (rawStatus.type === 'busy' || rawStatus.type === 'retry')) {
|
|
@@ -487,7 +487,7 @@ class OpenCodeCLI {
|
|
|
487
487
|
hasNewMessage: !!checkRes.hasNewMessage
|
|
488
488
|
};
|
|
489
489
|
} catch (error) {
|
|
490
|
-
this._log('
|
|
490
|
+
this._log('debug', `checkSessionStatus 异常 id=${sessionId}: ${error.message}`);
|
|
491
491
|
return { status: 'unknown' };
|
|
492
492
|
}
|
|
493
493
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { join, dirname } from 'node:path';
|
|
1
|
+
import { join, dirname, resolve } from 'node:path';
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
3
|
|
|
4
4
|
import JsonAdapter from './adapters/JsonAdapter.js';
|
|
@@ -36,6 +36,12 @@ class StorageManager {
|
|
|
36
36
|
constructor(options = {}) {
|
|
37
37
|
if (options.rootDir) {
|
|
38
38
|
this.rootDir = options.rootDir;
|
|
39
|
+
} else if (process.env['3LIB_DATA_DIR']) {
|
|
40
|
+
// CLI 数据目录策略(与 WEB_STATS_ROOT 同模式):
|
|
41
|
+
// 3rdloop serve/run 等命令将解析后的数据目录注入 env,
|
|
42
|
+
// 使内部所有自建 createStorage()(FlexRunner/KnowledgeManager/
|
|
43
|
+
// MessageArchive 等)统一落到用户数据目录,而非模块相对路径。
|
|
44
|
+
this.rootDir = resolve(process.env['3LIB_DATA_DIR']);
|
|
39
45
|
} else {
|
|
40
46
|
// 本文件 (Server/DbUse/) 上溯 3 层到 workspace 根 → db/
|
|
41
47
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
@@ -776,6 +776,77 @@ class FlexRunnerController {
|
|
|
776
776
|
}
|
|
777
777
|
}
|
|
778
778
|
|
|
779
|
+
/**
|
|
780
|
+
* POST /api/flexrunner/clean-source
|
|
781
|
+
* 删除任务目录下的源码克隆目录({taskDir}/source/),释放存储空间。
|
|
782
|
+
*
|
|
783
|
+
* 仓库检视(ArkTS/Flutter/RN)与 PR 代码检视(arkts/flutter/rn-code-check)均把
|
|
784
|
+
* 仓库源码克隆到任务目录的 source/ 固定路径;检视结束后源码不再被引用
|
|
785
|
+
* (检视报告 .md、meta .json、orchestration.json 等过程文件均在 taskDir 根目录),
|
|
786
|
+
* 由前端在任务终态时调用本接口清理。
|
|
787
|
+
*
|
|
788
|
+
* 安全约束:
|
|
789
|
+
* - 只删除固定子目录名 source(不接受任意路径参数,防误删报告/过程文件)
|
|
790
|
+
* - taskDir 经 _resolveTaskDir 解析(内存实例 → 编排元信息 → 磁盘白名单兜底)
|
|
791
|
+
* - 内存中编排器仍有步骤执行(pending/ready/running)时拒绝清理
|
|
792
|
+
* - source/ 不存在时幂等返回 cleaned=false(如 tpc-cpp-check 不克隆源码)
|
|
793
|
+
*
|
|
794
|
+
* Body: { taskId }
|
|
795
|
+
*/
|
|
796
|
+
async cleanSource(req, res) {
|
|
797
|
+
const body = req.body || {};
|
|
798
|
+
const { taskId } = body;
|
|
799
|
+
if (!taskId) {
|
|
800
|
+
return res.error(400, 'taskId 为必填项');
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
try {
|
|
804
|
+
// 活跃任务保护:内存中编排器仍有步骤在执行时拒绝(防止误删执行中正在使用的源码)
|
|
805
|
+
if (this._orchestratorController) {
|
|
806
|
+
try {
|
|
807
|
+
const steps = this._orchestratorController.getTaskSteps(taskId);
|
|
808
|
+
const active = steps.some(s =>
|
|
809
|
+
['pending', 'ready', 'running'].includes(String(s.status || '').toLowerCase()));
|
|
810
|
+
if (active) {
|
|
811
|
+
return res.error(409, '任务仍在执行中,暂不能清理源码');
|
|
812
|
+
}
|
|
813
|
+
} catch { /* 内存无实例(已结束 / 重启后磁盘恢复)→ 继续清理 */ }
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
const taskDir = await this._resolveTaskDir(taskId);
|
|
817
|
+
if (!taskDir) {
|
|
818
|
+
return res.error(404, `未找到任务目录: taskId=${taskId}`);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
const sourceDir = path.join(taskDir, 'source');
|
|
822
|
+
|
|
823
|
+
let st = null;
|
|
824
|
+
try { st = await fs.stat(sourceDir); } catch { /* 目录不存在 */ }
|
|
825
|
+
if (!st || !st.isDirectory()) {
|
|
826
|
+
return res.json({
|
|
827
|
+
status: 'ok',
|
|
828
|
+
cleaned: false,
|
|
829
|
+
freedBytes: 0,
|
|
830
|
+
message: '任务目录下无源码目录(source/ 不存在),无需清理',
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// 统计释放空间(统计失败不阻塞删除,按 0 返回)
|
|
835
|
+
const freedBytes = await this._dirSize(sourceDir).catch(() => 0);
|
|
836
|
+
await fs.rm(sourceDir, { recursive: true, force: true });
|
|
837
|
+
|
|
838
|
+
res.json({
|
|
839
|
+
status: 'ok',
|
|
840
|
+
cleaned: true,
|
|
841
|
+
freedBytes,
|
|
842
|
+
path: sourceDir,
|
|
843
|
+
message: '已删除源码目录 source/,报告与过程文件保留',
|
|
844
|
+
});
|
|
845
|
+
} catch (err) {
|
|
846
|
+
res.error(500, `源码清理失败: ${err.message}`);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
779
850
|
/**
|
|
780
851
|
* 解析任务工作目录(供 archive 打包等场景使用)。
|
|
781
852
|
*
|
|
@@ -823,6 +894,27 @@ class FlexRunnerController {
|
|
|
823
894
|
return null;
|
|
824
895
|
}
|
|
825
896
|
|
|
897
|
+
/**
|
|
898
|
+
* 递归统计目录占用大小(字节)。符号链接不计入(防循环遍历)。
|
|
899
|
+
* @param {string} dir
|
|
900
|
+
* @returns {Promise<number>}
|
|
901
|
+
* @private
|
|
902
|
+
*/
|
|
903
|
+
async _dirSize(dir) {
|
|
904
|
+
let total = 0;
|
|
905
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
906
|
+
for (const e of entries) {
|
|
907
|
+
const fp = path.join(dir, e.name);
|
|
908
|
+
if (e.isDirectory()) {
|
|
909
|
+
total += await this._dirSize(fp);
|
|
910
|
+
} else if (e.isFile()) {
|
|
911
|
+
const st = await fs.stat(fp).catch(() => null);
|
|
912
|
+
if (st) total += st.size;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
return total;
|
|
916
|
+
}
|
|
917
|
+
|
|
826
918
|
/**
|
|
827
919
|
* GET /api/flexrunner/runners
|
|
828
920
|
* 列出所有已注册的 FlexRunner 实例
|
|
@@ -21,6 +21,7 @@ import FlexRunnerController from '../controllers/FlexRunnerController.js';
|
|
|
21
21
|
* GET /steps — 列出任务步骤和文件
|
|
22
22
|
* GET /file — 读取指定文件内容
|
|
23
23
|
* GET /archive — 打包下载任务目录下全部 .md 报告(ZIP)
|
|
24
|
+
* POST /clean-source — 删除任务目录下源码克隆目录 source/(释放存储,保留报告/过程文件)
|
|
24
25
|
* GET /runners — 列出所有已注册实例
|
|
25
26
|
* DELETE /runner — 移除一个实例
|
|
26
27
|
*
|
|
@@ -51,6 +52,7 @@ export function createFlexRunnerRouter(dependencies) {
|
|
|
51
52
|
router.get('/steps', controller.steps.bind(controller));
|
|
52
53
|
router.get('/file', controller.file.bind(controller));
|
|
53
54
|
router.get('/archive', controller.archive.bind(controller));
|
|
55
|
+
router.post('/clean-source', controller.cleanSource.bind(controller));
|
|
54
56
|
router.get('/runners', controller.runners.bind(controller));
|
|
55
57
|
|
|
56
58
|
// ── 控制与检查 ──
|
package/vendor/VERSION
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
0.0.
|
|
2
|
-
built=2026-09-
|
|
3
|
-
sha=
|
|
1
|
+
0.0.6
|
|
2
|
+
built=2026-09-03T04:17:37.208Z
|
|
3
|
+
sha=3dae4a21a88fb4e7
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
* 步骤 2 arkts-library-interface-spec — 接口规格分析(复用已克隆源码)
|
|
10
10
|
* 步骤 3~6 stability / memoryLeak / — 4 个检视维度并发执行
|
|
11
11
|
* documentation / testCoverage (dependId=2 fan-out)
|
|
12
|
+
* 步骤 ⑦ 源码清理(前端动作,非编排步骤) — 终态后删除 source/ 释放存储,
|
|
13
|
+
* 仅删源码,报告与过程文件保留
|
|
12
14
|
*
|
|
13
15
|
* 每个检视 SKILL 输出 0-100 评分:
|
|
14
16
|
* - 首选:SKILL 按约定写 meta .json(stepId{X}_loop{L}_meta_{N}.json),
|
|
@@ -32,6 +34,7 @@
|
|
|
32
34
|
* 6. GET /api/flexrunner/steps?taskId= — 解析报告文件(懒加载)
|
|
33
35
|
* 7. GET /api/flexrunner/file?path= — 读取报告 / 评分兜底解析
|
|
34
36
|
* 8. POST /api/orchestrator/abort — 取消任务
|
|
37
|
+
* 9. POST /api/flexrunner/clean-source — 终态后清理源码(仅删 source/,报告保留)
|
|
35
38
|
*
|
|
36
39
|
* 依赖:utils.js(apiFetch / createPagination / escapeHtml / toast / $)
|
|
37
40
|
* codecheck.css(cc-* 通用卡片样式)+ arktslibrarycheck.css(akt-* 扩展样式)
|
|
@@ -47,6 +50,8 @@ var _AKT_STEP_DEFS = [
|
|
|
47
50
|
{ stepId: 4, key: 'memoryleak', skill: 'arkts-library-memory-leak-check', num: '④', name: '内存泄露检视', desc: '生命周期、NAPI 引用、事件订阅', report: '内存泄露检视报告.md', scoreKey: 'memoryLeakScore' },
|
|
48
51
|
{ stepId: 5, key: 'documentation', skill: 'arkts-library-documentation-quality-check', num: '⑤', name: '文档质量检视', desc: '完整性、准确性、鸿蒙化专项', report: '文档质量检视报告.md', scoreKey: 'documentationScore' },
|
|
49
52
|
{ stepId: 6, key: 'testcoverage', skill: 'arkts-library-test-coverage-check', num: '⑥', name: '用例覆盖度检视', desc: '接口覆盖率、测试质量', report: '用例覆盖度检视报告.md', scoreKey: 'testCoverageScore' },
|
|
53
|
+
// ⑦ 源码清理:纯前端动作(uiOnly,不进 taskDef),终态后调用 /api/flexrunner/clean-source 删除 source/
|
|
54
|
+
{ stepId: 7, key: 'cleansource', skill: '', num: '⑦', name: '源码清理', desc: '检视完成删除 source/ 源码释放存储,保留报告与过程文件', report: null, scoreKey: null, uiOnly: true },
|
|
50
55
|
];
|
|
51
56
|
|
|
52
57
|
/* 评分权重(与 AIweb ArktsLibraryCheck 综合评分口径一致,权重和 = 1) */
|
|
@@ -340,6 +345,7 @@ async function doArktsLibraryCheck() {
|
|
|
340
345
|
// 4 个检视步骤(并发):依赖步骤 1 已克隆的 source/ 和 步骤 2 生成的 接口规格说明.md
|
|
341
346
|
for (var i = 2; i < _AKT_STEP_DEFS.length; i++) {
|
|
342
347
|
var def = _AKT_STEP_DEFS[i];
|
|
348
|
+
if (def.uiOnly) continue; // 源码清理为纯前端动作,不进编排 taskDef
|
|
343
349
|
var desc = [
|
|
344
350
|
'请使用 ' + def.skill + ' SKILL 对 ArkTS 三方库「' + repoInfo.repoName + '」进行' + def.name + ',生成检视报告。',
|
|
345
351
|
'',
|
|
@@ -431,6 +437,7 @@ function _aktNewTaskState(taskId, repoName, repoUrl) {
|
|
|
431
437
|
_resolving: false,
|
|
432
438
|
_progressLoaded: false,
|
|
433
439
|
_terminalDone: false,
|
|
440
|
+
_cleanStarted: false, // 源码清理是否已触发(幂等防重)
|
|
434
441
|
mdFileCount: 0, // taskDir 下的 .md 文件数(驱动"下载全部报告"按钮)
|
|
435
442
|
reportPaths: {}, // 各维度专属报告文件(如 稳定性检视报告.md)
|
|
436
443
|
resultPaths: {}, // 各步骤 result .md 兜底路径(部分 SKILL 会把报告合入 result 文件)
|
|
@@ -1035,6 +1042,8 @@ function _aktOnTerminal(idx, status) {
|
|
|
1035
1042
|
_aktResolveFiles(idx).then(function () {
|
|
1036
1043
|
_aktFallbackScores(idx);
|
|
1037
1044
|
});
|
|
1045
|
+
// 最后环节:清理源码(仅删 taskDir/source/,报告 .md / meta .json 等过程文件保留)
|
|
1046
|
+
_aktCleanSource(idx);
|
|
1038
1047
|
}
|
|
1039
1048
|
|
|
1040
1049
|
function _aktFinishSSE(idx) {
|
|
@@ -1083,6 +1092,77 @@ function _aktRenderElapsed(idx) {
|
|
|
1083
1092
|
if (timeEl && task.startedAt) timeEl.textContent = _aktFormatTime(task.startedAt);
|
|
1084
1093
|
}
|
|
1085
1094
|
|
|
1095
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
1096
|
+
* 源码清理(检视最后环节:删除 taskDir/source/,节省存储)
|
|
1097
|
+
*
|
|
1098
|
+
* 后端 POST /api/flexrunner/clean-source 只删除任务目录下固定的 source/ 子目录
|
|
1099
|
+
* (源码克隆路径),检视报告 .md、meta .json、orchestration.json 等文档与
|
|
1100
|
+
* 过程文件均在 taskDir 根目录,不受影响。
|
|
1101
|
+
* 触发时机:任务终态(_aktOnTerminal);历史终态任务渲染时静默补删
|
|
1102
|
+
* (覆盖检视期间页面被关闭、清理未触发的场景)。
|
|
1103
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
1104
|
+
|
|
1105
|
+
/** 字节数格式化:B / KB / MB / GB */
|
|
1106
|
+
function _aktFormatBytes(n) {
|
|
1107
|
+
if (!isFinite(n) || n <= 0) return '0 B';
|
|
1108
|
+
var units = ['B', 'KB', 'MB', 'GB'];
|
|
1109
|
+
var i = 0;
|
|
1110
|
+
while (n >= 1024 && i < units.length - 1) { n /= 1024; i++; }
|
|
1111
|
+
return (i === 0 ? Math.round(n) : n.toFixed(1)) + ' ' + units[i];
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
/** 更新⑦"源码清理"步骤行状态(running / completed / failed) */
|
|
1115
|
+
function _aktSetCleanStatus(idx, status, freedBytes) {
|
|
1116
|
+
var row = document.getElementById('akt-' + idx + '-row-cleansource');
|
|
1117
|
+
var icon = document.getElementById('akt-' + idx + '-icon-cleansource');
|
|
1118
|
+
var txt = document.getElementById('akt-' + idx + '-cleansource-txt');
|
|
1119
|
+
if (row) row.className = 'akt-step-row ' + status;
|
|
1120
|
+
if (icon) {
|
|
1121
|
+
if (status === 'running') { icon.className = 'akt-step-icon running'; icon.textContent = '🧹'; }
|
|
1122
|
+
else if (status === 'completed') { icon.className = 'akt-step-icon completed'; icon.textContent = '✓'; }
|
|
1123
|
+
else if (status === 'failed') { icon.className = 'akt-step-icon failed'; icon.textContent = '✗'; }
|
|
1124
|
+
}
|
|
1125
|
+
if (txt) {
|
|
1126
|
+
if (status === 'running') txt.textContent = '正在清理源码…';
|
|
1127
|
+
else if (status === 'completed') txt.textContent = freedBytes != null ? '已清理源码,释放 ' + _aktFormatBytes(freedBytes) : '源码已清理';
|
|
1128
|
+
else if (status === 'failed') txt.textContent = '清理失败';
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
/**
|
|
1133
|
+
* 调用后端清理源码(幂等,失败允许下次触发重试)。
|
|
1134
|
+
* @param {number} idx
|
|
1135
|
+
* @param {boolean} [silent] - 静默模式(历史补删,不弹 toast)
|
|
1136
|
+
*/
|
|
1137
|
+
function _aktCleanSource(idx, silent) {
|
|
1138
|
+
var task = _aktTasks[idx];
|
|
1139
|
+
if (!task || task._cleanStarted) return;
|
|
1140
|
+
task._cleanStarted = true;
|
|
1141
|
+
|
|
1142
|
+
_aktSetCleanStatus(idx, 'running');
|
|
1143
|
+
apiFetch('POST', '/api/flexrunner/clean-source', { taskId: task.taskId }, 60000)
|
|
1144
|
+
.then(function (r) {
|
|
1145
|
+
if (r.data && r.data.status === 'ok' && r.data.cleaned) {
|
|
1146
|
+
_aktSetCleanStatus(idx, 'completed', r.data.freedBytes);
|
|
1147
|
+
if (!silent) {
|
|
1148
|
+
toast('info', '已清理源码(' + _aktEsc(task.repoName) + '),释放 ' + _aktFormatBytes(r.data.freedBytes) + ' 存储空间');
|
|
1149
|
+
}
|
|
1150
|
+
} else if (r.data && r.data.status === 'ok') {
|
|
1151
|
+
// 无源码可清(未克隆 / 已清理过)→ 幂等完成
|
|
1152
|
+
_aktSetCleanStatus(idx, 'completed');
|
|
1153
|
+
} else {
|
|
1154
|
+
// HTTP 错误(如 409 任务仍在执行)→ 失败可重试
|
|
1155
|
+
task._cleanStarted = false;
|
|
1156
|
+
_aktSetCleanStatus(idx, 'failed');
|
|
1157
|
+
}
|
|
1158
|
+
})
|
|
1159
|
+
.catch(function () {
|
|
1160
|
+
// 清理失败:重置标记,下次触发(如历史重新渲染)时重试
|
|
1161
|
+
task._cleanStarted = false;
|
|
1162
|
+
_aktSetCleanStatus(idx, 'failed');
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1086
1166
|
/* ════════════════════════════════════════════════════════════════════
|
|
1087
1167
|
* 停止检视
|
|
1088
1168
|
* ════════════════════════════════════════════════════════════════════ */
|
|
@@ -1387,6 +1467,8 @@ function _aktRenderHistoryPage(tasks, isFirstPage) {
|
|
|
1387
1467
|
_aktStartProgressPoll(idx);
|
|
1388
1468
|
} else {
|
|
1389
1469
|
_aktFinishSSE(idx);
|
|
1470
|
+
// 终态历史任务:静默补删源码(覆盖检视期间页面关闭、终态清理未触发的场景)
|
|
1471
|
+
_aktCleanSource(idx, true);
|
|
1390
1472
|
}
|
|
1391
1473
|
}
|
|
1392
1474
|
}
|
package/web/js/codecheck.js
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* 6. GET /api/flexrunner/steps?taskId= — 获取结果文件列表
|
|
14
14
|
* 7. GET /api/flexrunner/file?path= — 读取检视报告
|
|
15
15
|
* 8. POST /api/orchestrator/abort — 取消任务
|
|
16
|
+
* 9. POST /api/flexrunner/clean-source — 终态后清理源码(仅删 source/,报告保留)
|
|
16
17
|
*
|
|
17
18
|
* 依赖:utils.js(apiFetch / API / createPagination / escapeHtml / toast / $)
|
|
18
19
|
* ════════════════════════════════════════════════════════════════════ */
|
|
@@ -322,6 +323,7 @@ function _renderCCHistoryPage(tasks, isFirstPage) {
|
|
|
322
323
|
startedAt: startTime || null,
|
|
323
324
|
finishedAt: endTime || null,
|
|
324
325
|
resultPath: null,
|
|
326
|
+
_cleanStarted: false, // 源码清理是否已触发(幂等防重)
|
|
325
327
|
// 列表响应中的结构化指标(终态任务直接渲染徽章,无需额外请求)
|
|
326
328
|
metrics: t.metadata || null,
|
|
327
329
|
};
|
|
@@ -372,6 +374,8 @@ function _handleTaskStage(idx, stage) {
|
|
|
372
374
|
_startProgressPoll(idx);
|
|
373
375
|
} else {
|
|
374
376
|
_finishCCSSE(idx);
|
|
377
|
+
// 终态历史任务:静默补删源码(覆盖检视期间页面关闭、终态清理未触发的场景)
|
|
378
|
+
_ccCleanSource(idx, true);
|
|
375
379
|
// 终态任务不主动拉文件(上千任务时列表渲染零额外请求);
|
|
376
380
|
// 展开卡片 / 点击预览/下载时才懒加载(toggleCCCard / viewCCReport / downloadCCReport)
|
|
377
381
|
}
|
|
@@ -460,6 +464,7 @@ async function doCodeCheck() {
|
|
|
460
464
|
startedAt: null,
|
|
461
465
|
finishedAt: null,
|
|
462
466
|
resultPath: null,
|
|
467
|
+
_cleanStarted: false, // 源码清理是否已触发(幂等防重)
|
|
463
468
|
};
|
|
464
469
|
|
|
465
470
|
_createCCCard(idx, displayLabel, prUrl, libType, new Date().toISOString());
|
|
@@ -516,6 +521,8 @@ function _startProgressPoll(idx) {
|
|
|
516
521
|
_stopProgressPoll(idx);
|
|
517
522
|
_finishCCSSE(idx);
|
|
518
523
|
_loadCCResultFiles(idx);
|
|
524
|
+
// 最后环节:清理源码(仅删 taskDir/source/,报告与过程文件保留)
|
|
525
|
+
_ccCleanSource(idx);
|
|
519
526
|
}
|
|
520
527
|
} catch (e) { /* 静默失败 */ }
|
|
521
528
|
}, 3000);
|
|
@@ -610,6 +617,52 @@ async function _loadCCResultFiles(idx) {
|
|
|
610
617
|
} catch (e) { /* 静默失败 */ }
|
|
611
618
|
}
|
|
612
619
|
|
|
620
|
+
/* ── 源码清理(检视最后环节:删除 taskDir/source/,节省存储)──────────
|
|
621
|
+
*
|
|
622
|
+
* arkts/flutter/rn-code-check 检视过程中会把 PR 目标仓库克隆到任务目录的
|
|
623
|
+
* source/ 下,检视终态后源码不再被引用(报告 .md / meta .json 等过程文件
|
|
624
|
+
* 均在 taskDir 根目录,不受影响);tpc-cpp-check 不克隆源码,后端幂等返回。
|
|
625
|
+
* 触发时机:任务终态(轮询 / SSE complete / 取消);历史终态任务渲染时
|
|
626
|
+
* 静默补删(覆盖检视期间页面被关闭、清理未触发的场景)。
|
|
627
|
+
* ──────────────────────────────────────────────────────────────────── */
|
|
628
|
+
|
|
629
|
+
/** 字节数格式化:B / KB / MB / GB */
|
|
630
|
+
function _ccFormatBytes(n) {
|
|
631
|
+
if (!isFinite(n) || n <= 0) return '0 B';
|
|
632
|
+
var units = ['B', 'KB', 'MB', 'GB'];
|
|
633
|
+
var i = 0;
|
|
634
|
+
while (n >= 1024 && i < units.length - 1) { n /= 1024; i++; }
|
|
635
|
+
return (i === 0 ? Math.round(n) : n.toFixed(1)) + ' ' + units[i];
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* 调用后端清理源码(幂等,失败允许下次触发重试)。
|
|
640
|
+
* @param {number} idx
|
|
641
|
+
* @param {boolean} [silent] - 静默模式(历史补删 / 取消场景,不弹 toast)
|
|
642
|
+
*/
|
|
643
|
+
function _ccCleanSource(idx, silent) {
|
|
644
|
+
var task = _ccTasks[idx];
|
|
645
|
+
if (!task || task._cleanStarted) return;
|
|
646
|
+
task._cleanStarted = true;
|
|
647
|
+
|
|
648
|
+
apiFetch('POST', '/api/flexrunner/clean-source', { taskId: task.taskId }, 60000)
|
|
649
|
+
.then(function (r) {
|
|
650
|
+
if (r.data && r.data.status === 'ok' && r.data.cleaned) {
|
|
651
|
+
if (!silent) {
|
|
652
|
+
toast('info', '已清理检视源码,释放 ' + _ccFormatBytes(r.data.freedBytes) + ' 存储空间');
|
|
653
|
+
}
|
|
654
|
+
} else if (!(r.data && r.data.status === 'ok')) {
|
|
655
|
+
// HTTP 错误(如 409 任务仍在执行)→ 失败可重试
|
|
656
|
+
task._cleanStarted = false;
|
|
657
|
+
}
|
|
658
|
+
// ok 且 cleaned=false:无源码可清(未克隆 / 已清理过)→ 幂等完成,不提示
|
|
659
|
+
})
|
|
660
|
+
.catch(function () {
|
|
661
|
+
// 清理失败:重置标记,下次触发(如历史重新渲染)时重试
|
|
662
|
+
task._cleanStarted = false;
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
|
|
613
666
|
/* ── 创建检视任务卡片 ────────────────────────────────────────────────── */
|
|
614
667
|
function _createCCCard(idx, displayLabel, prUrl, libType, createdAt, isHistory) {
|
|
615
668
|
var emptyEl = $('cc-tasks-empty');
|
|
@@ -764,6 +817,8 @@ function _connectCCSSE(idx) {
|
|
|
764
817
|
_stopProgressPoll(idx);
|
|
765
818
|
_finishCCSSE(idx);
|
|
766
819
|
_loadCCResultFiles(idx);
|
|
820
|
+
// 最后环节:清理源码(仅删 taskDir/source/,报告与过程文件保留)
|
|
821
|
+
_ccCleanSource(idx);
|
|
767
822
|
});
|
|
768
823
|
|
|
769
824
|
// 编排错误
|
|
@@ -777,6 +832,8 @@ function _connectCCSSE(idx) {
|
|
|
777
832
|
_updateCCStage(idx, 'aborted', {});
|
|
778
833
|
_stopProgressPoll(idx);
|
|
779
834
|
_finishCCSSE(idx);
|
|
835
|
+
// 终止场景:静默清理源码(已有"已取消"提示,避免 toast 叠加)
|
|
836
|
+
_ccCleanSource(idx, true);
|
|
780
837
|
});
|
|
781
838
|
|
|
782
839
|
// 连接建立
|
|
@@ -920,6 +977,8 @@ async function stopCCTask(idx) {
|
|
|
920
977
|
_updateCCStage(idx, 'aborted', {});
|
|
921
978
|
_stopProgressPoll(idx);
|
|
922
979
|
_finishCCSSE(idx);
|
|
980
|
+
// 终止场景:静默清理源码(SSE aborted 监听会幂等兜底)
|
|
981
|
+
_ccCleanSource(idx, true);
|
|
923
982
|
}
|
|
924
983
|
|
|
925
984
|
/* ── 报告下载 ────────────────────────────────────────────────────────── */
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* 步骤 1 flutter-interface — 克隆仓库 + 接口规格分析
|
|
9
9
|
* 步骤 2~6 stability / consistency / memoryLeak / — 5 个检视维度并发执行
|
|
10
10
|
* documentation / testCoverage (dependId=1 fan-out)
|
|
11
|
+
* 步骤 ⑦ 源码清理(前端动作,非编排步骤) — 终态后删除 source/ 释放存储,
|
|
12
|
+
* 仅删源码,报告与过程文件保留
|
|
11
13
|
*
|
|
12
14
|
* 每个检视 SKILL 输出 0-100 评分:
|
|
13
15
|
* - 首选:SKILL 按约定写 meta .json(stepId{X}_loop{L}_meta_{N}.json),
|
|
@@ -24,6 +26,7 @@
|
|
|
24
26
|
* 6. GET /api/flexrunner/steps?taskId= — 解析报告文件(懒加载)
|
|
25
27
|
* 7. GET /api/flexrunner/file?path= — 读取报告 / 评分兜底解析
|
|
26
28
|
* 8. POST /api/orchestrator/abort — 取消任务
|
|
29
|
+
* 9. POST /api/flexrunner/clean-source — 终态后清理源码(仅删 source/,报告保留)
|
|
27
30
|
*
|
|
28
31
|
* 依赖:utils.js(apiFetch / createPagination / escapeHtml / toast / $)
|
|
29
32
|
* codecheck.css(cc-* 通用卡片样式)+ flutterlibrarycheck.css(flc-* 扩展样式)
|
|
@@ -39,6 +42,8 @@ var _FLC_STEP_DEFS = [
|
|
|
39
42
|
{ stepId: 4, key: 'memoryleak', skill: 'flutter-library-memory-leak-check', num: '④', name: '内存泄露检视', desc: '生命周期、资源释放、Stream 管理', report: '内存泄露检视报告.md', scoreKey: 'memoryLeakScore' },
|
|
40
43
|
{ stepId: 5, key: 'documentation', skill: 'flutter-library-documentation-quality-check', num: '⑤', name: '文档质量检视', desc: '鸿蒙化文档完整性、准确性、易读性', report: '文档质量检视报告.md', scoreKey: 'documentationScore' },
|
|
41
44
|
{ stepId: 6, key: 'testcoverage', skill: 'flutter-library-test-coverage-check', num: '⑥', name: '用例覆盖度检视', desc: '测试覆盖率、Demo 覆盖率', report: '用例覆盖度检视报告.md', scoreKey: 'testCoverageScore' },
|
|
45
|
+
// ⑦ 源码清理:纯前端动作(uiOnly,不进 taskDef),终态后调用 /api/flexrunner/clean-source 删除 source/
|
|
46
|
+
{ stepId: 7, key: 'cleansource', skill: '', num: '⑦', name: '源码清理', desc: '检视完成删除 source/ 源码释放存储,保留报告与过程文件', report: null, scoreKey: null, uiOnly: true },
|
|
42
47
|
];
|
|
43
48
|
|
|
44
49
|
/* 评分权重(与 AIweb FlutterLibraryCheck 综合评分口径一致,权重和 = 1) */
|
|
@@ -301,6 +306,7 @@ async function doFlutterLibraryCheck() {
|
|
|
301
306
|
// 5 个检视步骤(并发):依赖步骤 1 已克隆的 source/ 和 接口规格说明.md
|
|
302
307
|
for (var i = 1; i < _FLC_STEP_DEFS.length; i++) {
|
|
303
308
|
var def = _FLC_STEP_DEFS[i];
|
|
309
|
+
if (def.uiOnly) continue; // 源码清理为纯前端动作,不进编排 taskDef
|
|
304
310
|
var desc = [
|
|
305
311
|
'请使用 ' + def.skill + ' SKILL 对 Flutter 三方库「' + repoInfo.repoName + '」进行' + def.name + ',生成检视报告。',
|
|
306
312
|
'',
|
|
@@ -383,6 +389,7 @@ function _flcNewTaskState(taskId, repoUrl, repoName) {
|
|
|
383
389
|
_resolving: false,
|
|
384
390
|
_progressLoaded: false,
|
|
385
391
|
_terminalDone: false,
|
|
392
|
+
_cleanStarted: false, // 源码清理是否已触发(幂等防重)
|
|
386
393
|
mdFileCount: 0, // taskDir 下的 .md 文件数(驱动"下载全部报告"按钮)
|
|
387
394
|
reportPaths: {}, // 各维度专属报告文件(如 稳定性检视报告.md)
|
|
388
395
|
resultPaths: {}, // 各步骤 result .md 兜底路径(部分 SKILL 会把报告合入 result 文件)
|
|
@@ -826,9 +833,10 @@ async function _flcResolveFiles(idx) {
|
|
|
826
833
|
var task = _flcTasks[idx];
|
|
827
834
|
if (!task) return;
|
|
828
835
|
|
|
829
|
-
// 全部专属报告已找到 →
|
|
836
|
+
// 全部专属报告已找到 → 幂等返回(uiOnly / 无报告步骤跳过,如⑦源码清理)
|
|
830
837
|
var allFound = true;
|
|
831
838
|
for (var a = 0; a < _FLC_STEP_DEFS.length; a++) {
|
|
839
|
+
if (_FLC_STEP_DEFS[a].uiOnly || !_FLC_STEP_DEFS[a].report) continue;
|
|
832
840
|
if (!task.reportPaths[_FLC_STEP_DEFS[a].key]) { allFound = false; break; }
|
|
833
841
|
}
|
|
834
842
|
if (allFound) { task.filesResolved = true; _flcShowReportButtons(idx); return; }
|
|
@@ -986,6 +994,8 @@ function _flcOnTerminal(idx, status) {
|
|
|
986
994
|
_flcResolveFiles(idx).then(function () {
|
|
987
995
|
_flcFallbackScores(idx);
|
|
988
996
|
});
|
|
997
|
+
// 最后环节:清理源码(仅删 taskDir/source/,报告 .md / meta .json 等过程文件保留)
|
|
998
|
+
_flcCleanSource(idx);
|
|
989
999
|
}
|
|
990
1000
|
|
|
991
1001
|
function _flcFinishSSE(idx) {
|
|
@@ -1034,6 +1044,77 @@ function _flcRenderElapsed(idx) {
|
|
|
1034
1044
|
if (timeEl && task.startedAt) timeEl.textContent = _flcFormatTime(task.startedAt);
|
|
1035
1045
|
}
|
|
1036
1046
|
|
|
1047
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
1048
|
+
* 源码清理(检视最后环节:删除 taskDir/source/,节省存储)
|
|
1049
|
+
*
|
|
1050
|
+
* 后端 POST /api/flexrunner/clean-source 只删除任务目录下固定的 source/ 子目录
|
|
1051
|
+
* (源码克隆路径),检视报告 .md、meta .json、orchestration.json 等文档与
|
|
1052
|
+
* 过程文件均在 taskDir 根目录,不受影响。
|
|
1053
|
+
* 触发时机:任务终态(_flcOnTerminal);历史终态任务渲染时静默补删
|
|
1054
|
+
* (覆盖检视期间页面被关闭、清理未触发的场景)。
|
|
1055
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
1056
|
+
|
|
1057
|
+
/** 字节数格式化:B / KB / MB / GB */
|
|
1058
|
+
function _flcFormatBytes(n) {
|
|
1059
|
+
if (!isFinite(n) || n <= 0) return '0 B';
|
|
1060
|
+
var units = ['B', 'KB', 'MB', 'GB'];
|
|
1061
|
+
var i = 0;
|
|
1062
|
+
while (n >= 1024 && i < units.length - 1) { n /= 1024; i++; }
|
|
1063
|
+
return (i === 0 ? Math.round(n) : n.toFixed(1)) + ' ' + units[i];
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
/** 更新⑦"源码清理"步骤行状态(running / completed / failed) */
|
|
1067
|
+
function _flcSetCleanStatus(idx, status, freedBytes) {
|
|
1068
|
+
var row = document.getElementById('flc-' + idx + '-row-cleansource');
|
|
1069
|
+
var icon = document.getElementById('flc-' + idx + '-icon-cleansource');
|
|
1070
|
+
var txt = document.getElementById('flc-' + idx + '-cleansource-txt');
|
|
1071
|
+
if (row) row.className = 'flc-step-row ' + status;
|
|
1072
|
+
if (icon) {
|
|
1073
|
+
if (status === 'running') { icon.className = 'flc-step-icon running'; icon.textContent = '🧹'; }
|
|
1074
|
+
else if (status === 'completed') { icon.className = 'flc-step-icon completed'; icon.textContent = '✓'; }
|
|
1075
|
+
else if (status === 'failed') { icon.className = 'flc-step-icon failed'; icon.textContent = '✗'; }
|
|
1076
|
+
}
|
|
1077
|
+
if (txt) {
|
|
1078
|
+
if (status === 'running') txt.textContent = '正在清理源码…';
|
|
1079
|
+
else if (status === 'completed') txt.textContent = freedBytes != null ? '已清理源码,释放 ' + _flcFormatBytes(freedBytes) : '源码已清理';
|
|
1080
|
+
else if (status === 'failed') txt.textContent = '清理失败';
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
/**
|
|
1085
|
+
* 调用后端清理源码(幂等,失败允许下次触发重试)。
|
|
1086
|
+
* @param {number} idx
|
|
1087
|
+
* @param {boolean} [silent] - 静默模式(历史补删,不弹 toast)
|
|
1088
|
+
*/
|
|
1089
|
+
function _flcCleanSource(idx, silent) {
|
|
1090
|
+
var task = _flcTasks[idx];
|
|
1091
|
+
if (!task || task._cleanStarted) return;
|
|
1092
|
+
task._cleanStarted = true;
|
|
1093
|
+
|
|
1094
|
+
_flcSetCleanStatus(idx, 'running');
|
|
1095
|
+
apiFetch('POST', '/api/flexrunner/clean-source', { taskId: task.taskId }, 60000)
|
|
1096
|
+
.then(function (r) {
|
|
1097
|
+
if (r.data && r.data.status === 'ok' && r.data.cleaned) {
|
|
1098
|
+
_flcSetCleanStatus(idx, 'completed', r.data.freedBytes);
|
|
1099
|
+
if (!silent) {
|
|
1100
|
+
toast('info', '已清理源码(' + _flcEsc(task.repoName) + '),释放 ' + _flcFormatBytes(r.data.freedBytes) + ' 存储空间');
|
|
1101
|
+
}
|
|
1102
|
+
} else if (r.data && r.data.status === 'ok') {
|
|
1103
|
+
// 无源码可清(未克隆 / 已清理过)→ 幂等完成
|
|
1104
|
+
_flcSetCleanStatus(idx, 'completed');
|
|
1105
|
+
} else {
|
|
1106
|
+
// HTTP 错误(如 409 任务仍在执行)→ 失败可重试
|
|
1107
|
+
task._cleanStarted = false;
|
|
1108
|
+
_flcSetCleanStatus(idx, 'failed');
|
|
1109
|
+
}
|
|
1110
|
+
})
|
|
1111
|
+
.catch(function () {
|
|
1112
|
+
// 清理失败:重置标记,下次触发(如历史重新渲染)时重试
|
|
1113
|
+
task._cleanStarted = false;
|
|
1114
|
+
_flcSetCleanStatus(idx, 'failed');
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1037
1118
|
/* ════════════════════════════════════════════════════════════════════
|
|
1038
1119
|
* 停止检视
|
|
1039
1120
|
* ════════════════════════════════════════════════════════════════════ */
|
|
@@ -1338,6 +1419,8 @@ function _flcRenderHistoryPage(tasks, isFirstPage) {
|
|
|
1338
1419
|
_flcStartProgressPoll(idx);
|
|
1339
1420
|
} else {
|
|
1340
1421
|
_flcFinishSSE(idx);
|
|
1422
|
+
// 终态历史任务:静默补删源码(覆盖检视期间页面关闭、终态清理未触发的场景)
|
|
1423
|
+
_flcCleanSource(idx, true);
|
|
1341
1424
|
}
|
|
1342
1425
|
}
|
|
1343
1426
|
}
|
package/web/js/rnlibrarycheck.js
CHANGED
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
* 步骤 2~7 stability / consistency / memoryLeak / — 6 个检视维度并发执行
|
|
10
10
|
* documentation / testCoverage / (dependId=1 fan-out)
|
|
11
11
|
* knownIssue(独立扣分项)
|
|
12
|
+
* 步骤 ⑧ 源码清理(前端动作,非编排步骤) — 终态后删除 source/ 释放存储
|
|
13
|
+
* (ohos/ + RN/),仅删源码,
|
|
14
|
+
* 报告与过程文件保留
|
|
12
15
|
*
|
|
13
16
|
* 每个检视 SKILL 输出 0-100 评分:
|
|
14
17
|
* - 首选:SKILL 按约定写 meta .json(stepId{X}_loop{L}_meta_{N}.json),
|
|
@@ -33,6 +36,7 @@
|
|
|
33
36
|
* 6. GET /api/flexrunner/steps?taskId= — 解析报告文件(懒加载)
|
|
34
37
|
* 7. GET /api/flexrunner/file?path= — 读取报告 / 评分兜底解析
|
|
35
38
|
* 8. POST /api/orchestrator/abort — 取消任务
|
|
39
|
+
* 9. POST /api/flexrunner/clean-source — 终态后清理源码(仅删 source/,报告保留)
|
|
36
40
|
*
|
|
37
41
|
* 依赖:utils.js(apiFetch / createPagination / escapeHtml / toast / $)
|
|
38
42
|
* codecheck.css(cc-* 通用卡片样式)+ rnlibrarycheck.css(rn-* 扩展样式)
|
|
@@ -49,6 +53,8 @@ var _RN_STEP_DEFS = [
|
|
|
49
53
|
{ stepId: 5, key: 'documentation', skill: 'rn-library-documentation-quality-check', num: '⑤', name: '文档质量检视', desc: 'API 注释、适配说明、示例质量', report: '文档质量检视报告.md', scoreKey: 'documentationScore' },
|
|
50
54
|
{ stepId: 6, key: 'testcoverage', skill: 'rn-library-test-coverage-check', num: '⑥', name: '用例覆盖度检视', desc: '测试覆盖率、Demo 覆盖率', report: '用例覆盖度检视报告.md', scoreKey: 'testCoverageScore' },
|
|
51
55
|
{ stepId: 7, key: 'knownissue', skill: 'rn-library-known-issue-check', num: '⑦', name: '已知问题检视', desc: '历史案例规则回归,独立扣分项(命中一条扣 30 分)', report: '已知问题检视报告.md', scoreKey: 'knownIssueScore' },
|
|
56
|
+
// ⑧ 源码清理:纯前端动作(uiOnly,不进 taskDef),终态后调用 /api/flexrunner/clean-source 删除 source/(ohos/ + RN/)
|
|
57
|
+
{ stepId: 8, key: 'cleansource', skill: '', num: '⑧', name: '源码清理', desc: '检视完成删除 source/ 源码释放存储,保留报告与过程文件', report: null, scoreKey: null, uiOnly: true },
|
|
52
58
|
];
|
|
53
59
|
|
|
54
60
|
/* 评分权重(与 AIweb RNLibraryCheck 综合评分口径一致,权重和 = 1;knownissue 为独立扣分项不参与加权) */
|
|
@@ -283,6 +289,7 @@ async function doRNLibraryCheck() {
|
|
|
283
289
|
// 6 个检视步骤(并发):依赖步骤 1 已克隆的 source/ohos/、source/RN/ 和 接口规格说明.md
|
|
284
290
|
for (var i = 1; i < _RN_STEP_DEFS.length; i++) {
|
|
285
291
|
var def = _RN_STEP_DEFS[i];
|
|
292
|
+
if (def.uiOnly) continue; // 源码清理为纯前端动作,不进编排 taskDef
|
|
286
293
|
var desc = [
|
|
287
294
|
'请使用 ' + def.skill + ' SKILL 对 RN 三方库「' + libraryName + '」进行' + def.name + ',生成检视报告。',
|
|
288
295
|
'',
|
|
@@ -384,6 +391,7 @@ function _rnNewTaskState(taskId, libraryName, ohosRepoUrl, upstreamRepoUrl, rndo
|
|
|
384
391
|
_resolving: false,
|
|
385
392
|
_progressLoaded: false,
|
|
386
393
|
_terminalDone: false,
|
|
394
|
+
_cleanStarted: false, // 源码清理是否已触发(幂等防重)
|
|
387
395
|
mdFileCount: 0, // taskDir 下的 .md 文件数(驱动"下载全部报告"按钮)
|
|
388
396
|
reportPaths: {}, // 各维度专属报告文件(如 稳定性检视报告.md)
|
|
389
397
|
resultPaths: {}, // 各步骤 result .md 兜底路径(部分 SKILL 会把报告合入 result 文件)
|
|
@@ -837,9 +845,10 @@ async function _rnResolveFiles(idx) {
|
|
|
837
845
|
var task = _rnTasks[idx];
|
|
838
846
|
if (!task) return;
|
|
839
847
|
|
|
840
|
-
// 全部专属报告已找到 →
|
|
848
|
+
// 全部专属报告已找到 → 幂等返回(uiOnly / 无报告步骤跳过,如⑧源码清理)
|
|
841
849
|
var allFound = true;
|
|
842
850
|
for (var a = 0; a < _RN_STEP_DEFS.length; a++) {
|
|
851
|
+
if (_RN_STEP_DEFS[a].uiOnly || !_RN_STEP_DEFS[a].report) continue;
|
|
843
852
|
if (!task.reportPaths[_RN_STEP_DEFS[a].key]) { allFound = false; break; }
|
|
844
853
|
}
|
|
845
854
|
if (allFound) { task.filesResolved = true; _rnShowReportButtons(idx); return; }
|
|
@@ -997,6 +1006,8 @@ function _rnOnTerminal(idx, status) {
|
|
|
997
1006
|
_rnResolveFiles(idx).then(function () {
|
|
998
1007
|
_rnFallbackScores(idx);
|
|
999
1008
|
});
|
|
1009
|
+
// 最后环节:清理源码(仅删 taskDir/source/(ohos/ + RN/),报告 .md / meta .json 等过程文件保留)
|
|
1010
|
+
_rnCleanSource(idx);
|
|
1000
1011
|
}
|
|
1001
1012
|
|
|
1002
1013
|
function _rnFinishSSE(idx) {
|
|
@@ -1045,6 +1056,77 @@ function _rnRenderElapsed(idx) {
|
|
|
1045
1056
|
if (timeEl && task.startedAt) timeEl.textContent = _rnFormatTime(task.startedAt);
|
|
1046
1057
|
}
|
|
1047
1058
|
|
|
1059
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
1060
|
+
* 源码清理(检视最后环节:删除 taskDir/source/,节省存储)
|
|
1061
|
+
*
|
|
1062
|
+
* 后端 POST /api/flexrunner/clean-source 只删除任务目录下固定的 source/ 子目录
|
|
1063
|
+
* (RN 双仓库源码克隆路径,含 ohos/ 与 RN/),检视报告 .md、meta .json、
|
|
1064
|
+
* orchestration.json 等文档与过程文件均在 taskDir 根目录,不受影响。
|
|
1065
|
+
* 触发时机:任务终态(_rnOnTerminal);历史终态任务渲染时静默补删
|
|
1066
|
+
* (覆盖检视期间页面被关闭、清理未触发的场景)。
|
|
1067
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
1068
|
+
|
|
1069
|
+
/** 字节数格式化:B / KB / MB / GB */
|
|
1070
|
+
function _rnFormatBytes(n) {
|
|
1071
|
+
if (!isFinite(n) || n <= 0) return '0 B';
|
|
1072
|
+
var units = ['B', 'KB', 'MB', 'GB'];
|
|
1073
|
+
var i = 0;
|
|
1074
|
+
while (n >= 1024 && i < units.length - 1) { n /= 1024; i++; }
|
|
1075
|
+
return (i === 0 ? Math.round(n) : n.toFixed(1)) + ' ' + units[i];
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
/** 更新⑧"源码清理"步骤行状态(running / completed / failed) */
|
|
1079
|
+
function _rnSetCleanStatus(idx, status, freedBytes) {
|
|
1080
|
+
var row = document.getElementById('rn-' + idx + '-row-cleansource');
|
|
1081
|
+
var icon = document.getElementById('rn-' + idx + '-icon-cleansource');
|
|
1082
|
+
var txt = document.getElementById('rn-' + idx + '-cleansource-txt');
|
|
1083
|
+
if (row) row.className = 'rn-step-row ' + status;
|
|
1084
|
+
if (icon) {
|
|
1085
|
+
if (status === 'running') { icon.className = 'rn-step-icon running'; icon.textContent = '🧹'; }
|
|
1086
|
+
else if (status === 'completed') { icon.className = 'rn-step-icon completed'; icon.textContent = '✓'; }
|
|
1087
|
+
else if (status === 'failed') { icon.className = 'rn-step-icon failed'; icon.textContent = '✗'; }
|
|
1088
|
+
}
|
|
1089
|
+
if (txt) {
|
|
1090
|
+
if (status === 'running') txt.textContent = '正在清理源码…';
|
|
1091
|
+
else if (status === 'completed') txt.textContent = freedBytes != null ? '已清理源码,释放 ' + _rnFormatBytes(freedBytes) : '源码已清理';
|
|
1092
|
+
else if (status === 'failed') txt.textContent = '清理失败';
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
/**
|
|
1097
|
+
* 调用后端清理源码(幂等,失败允许下次触发重试)。
|
|
1098
|
+
* @param {number} idx
|
|
1099
|
+
* @param {boolean} [silent] - 静默模式(历史补删,不弹 toast)
|
|
1100
|
+
*/
|
|
1101
|
+
function _rnCleanSource(idx, silent) {
|
|
1102
|
+
var task = _rnTasks[idx];
|
|
1103
|
+
if (!task || task._cleanStarted) return;
|
|
1104
|
+
task._cleanStarted = true;
|
|
1105
|
+
|
|
1106
|
+
_rnSetCleanStatus(idx, 'running');
|
|
1107
|
+
apiFetch('POST', '/api/flexrunner/clean-source', { taskId: task.taskId }, 60000)
|
|
1108
|
+
.then(function (r) {
|
|
1109
|
+
if (r.data && r.data.status === 'ok' && r.data.cleaned) {
|
|
1110
|
+
_rnSetCleanStatus(idx, 'completed', r.data.freedBytes);
|
|
1111
|
+
if (!silent) {
|
|
1112
|
+
toast('info', '已清理源码(' + _rnEsc(task.libraryName) + '),释放 ' + _rnFormatBytes(r.data.freedBytes) + ' 存储空间');
|
|
1113
|
+
}
|
|
1114
|
+
} else if (r.data && r.data.status === 'ok') {
|
|
1115
|
+
// 无源码可清(未克隆 / 已清理过)→ 幂等完成
|
|
1116
|
+
_rnSetCleanStatus(idx, 'completed');
|
|
1117
|
+
} else {
|
|
1118
|
+
// HTTP 错误(如 409 任务仍在执行)→ 失败可重试
|
|
1119
|
+
task._cleanStarted = false;
|
|
1120
|
+
_rnSetCleanStatus(idx, 'failed');
|
|
1121
|
+
}
|
|
1122
|
+
})
|
|
1123
|
+
.catch(function () {
|
|
1124
|
+
// 清理失败:重置标记,下次触发(如历史重新渲染)时重试
|
|
1125
|
+
task._cleanStarted = false;
|
|
1126
|
+
_rnSetCleanStatus(idx, 'failed');
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1048
1130
|
/* ════════════════════════════════════════════════════════════════════
|
|
1049
1131
|
* 停止检视
|
|
1050
1132
|
* ════════════════════════════════════════════════════════════════════ */
|
|
@@ -1352,6 +1434,8 @@ function _rnRenderHistoryPage(tasks, isFirstPage) {
|
|
|
1352
1434
|
_rnStartProgressPoll(idx);
|
|
1353
1435
|
} else {
|
|
1354
1436
|
_rnFinishSSE(idx);
|
|
1437
|
+
// 终态历史任务:静默补删源码(覆盖检视期间页面关闭、终态清理未触发的场景)
|
|
1438
|
+
_rnCleanSource(idx, true);
|
|
1355
1439
|
}
|
|
1356
1440
|
}
|
|
1357
1441
|
}
|