deveco_hmigbot 0.1.0 → 0.1.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 +2 -0
- package/dist/index.js +1 -1
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/skills/migrate-core/SKILL.md +43 -38
- package/skills/migrate-core/references/flow/build-error-patterns.md +1 -1
- package/skills/migrate-core/references/flow/conventions-template.md +14 -9
- package/skills/migrate-core/references/host-capabilities.md +3 -3
- package/skills/migrate-core/references/topics/library-migration/stdlib-mapping-table.md +2 -2
- package/skills/migrate-core/scripts/closure/check-api-guards.mjs +44 -2
- package/skills/migrate-core/scripts/closure/check-safearea.mjs +20 -2
- package/skills/migrate-core/scripts/ledger/ledger.mjs +26 -12
- package/skills/migrate-core/scripts/smoke/walk.mjs +24 -3
- package/skills/migrate-core/templates/acceptance.js +15 -7
- package/skills/migrate-core/templates/mig_slices.js +32 -10
|
@@ -38,6 +38,8 @@
|
|
|
38
38
|
* 报告:每个失败步带 kind(target_missing/target_ambiguous/target_occluded 提示见 occludedBy/expect_missing/process_died/io_error)与页面字典;
|
|
39
39
|
* 崩溃带 crashDetail(Reason/Error message/首个应用帧);failStreak=同一 --out 连续失败次数;seen=全程见过的 text/id 并集
|
|
40
40
|
* 默认首个失败步即停(后续记 skipped,失败现场补一张 fail-<计划名>-step-<i>.png);--continue 继续跑完。run 结束自动查 faultlog:新增崩溃 → exit 10。
|
|
41
|
+
* --out 缺省落在计划旁 <计划名>.report.json(ledger render --walks 按报告内 plan 字段配对,别改名);同目录已有走查报告时,run 前先把计划里
|
|
42
|
+
* 没在任何报告 seen 字典出现过的 match 目标列出来(只提示不拦)——文案写错在上设备前就能看见,状态相关文案除外。
|
|
41
43
|
* 直达边界:`start --route` 经 smokeRoute 钩子直接推入目标页,**不经过首页**;首页承载的状态恢复(读偏好/数据库)不会执行,看到的是种子/默认态。
|
|
42
44
|
* 持久化类断言必须 `start`(不带 route)正常冷启 + 应用内导航;重启核持久化前先把状态改成非默认值,否则默认值证明不了任何事。
|
|
43
45
|
* 匹配:默认正则(不分大小写;非法正则自动退化为原文子串)撞 text/id/key;以 = 开头为全等(text 或 id);以 type: 开头撞组件类型。
|
|
@@ -46,7 +48,7 @@
|
|
|
46
48
|
* 退出码:0 通过 / 3 环境(无设备·多设备未指定·锁等待超时·参数错)/ 4 断言未命中·未存活·等待超时 / 5 匹配歧义或无命中 / 10 崩溃
|
|
47
49
|
*/
|
|
48
50
|
import { execFileSync } from 'node:child_process';
|
|
49
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync, rmSync, renameSync, mkdtempSync, realpathSync, unlinkSync } from 'node:fs';
|
|
51
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, rmSync, renameSync, mkdtempSync, realpathSync, unlinkSync, readdirSync } from 'node:fs';
|
|
50
52
|
import { join, resolve, dirname, relative, isAbsolute, sep } from 'node:path';
|
|
51
53
|
import { tmpdir } from 'node:os';
|
|
52
54
|
import { fileURLToPath } from 'node:url';
|
|
@@ -76,6 +78,22 @@ export function matchNodes(nodes, pattern) {
|
|
|
76
78
|
const re = safeRe(p);
|
|
77
79
|
return nodes.filter(n => (n.text && re.test(n.text)) || (n.id && re.test(n.id)) || (n.key && re.test(n.key)));
|
|
78
80
|
}
|
|
81
|
+
// 计划预核:对已有走查报告的 seen 字典(text/id 并集)撞计划里 tap/type/expect/wait/scrollUntil 的 match,没出现过的列出来;type: 匹配不核
|
|
82
|
+
export function unseenTargets(plan, seen) {
|
|
83
|
+
const pseudo = [...(seen.texts || []).map(t => ({ text: t, id: '', key: '', type: '' })), ...(seen.ids || []).map(i => ({ text: '', id: i, key: '', type: '' }))];
|
|
84
|
+
const out = [];
|
|
85
|
+
for (const [i, s] of (plan.steps || []).entries()) {
|
|
86
|
+
if (!['tap', 'type', 'expect', 'wait', 'scrollUntil'].includes(s.op) || s.match == null) continue;
|
|
87
|
+
for (const m of Array.isArray(s.match) ? s.match : [s.match]) { const q = String(m); if (q.startsWith('type:')) continue; if (!matchNodes(pseudo, q).length) out.push({ i, op: s.op, match: q }); }
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
export function siblingSeen(dir) {
|
|
92
|
+
const texts = new Set(), ids = new Set(); let count = 0;
|
|
93
|
+
let files = []; try { files = readdirSync(dir).filter(f => f.endsWith('.report.json')); } catch { files = []; }
|
|
94
|
+
for (const f of files) { let rep; try { rep = JSON.parse(readFileSync(join(dir, f), 'utf8')); } catch { continue; } if (!rep || !rep.seen) continue; count++; for (const t of rep.seen.texts || []) texts.add(t); for (const t of rep.seen.ids || []) ids.add(t); }
|
|
95
|
+
return { texts: [...texts], ids: [...ids], count };
|
|
96
|
+
}
|
|
79
97
|
// 遮挡判定(几何,按 ArkUI 命中测试同序):bindSheet/CustomDialog/Menu/Popup 的包装层挂在页面之后、盖住大半屏;
|
|
80
98
|
// 节点 N 之后出现、不是 N 后代、bounds 含 N 中心且面积 ≥ 屏幕一半的节点 M → N 被 M 盖住(点击落在 M 上)。
|
|
81
99
|
// 小面积的后续重叠(底栏/FAB 压住列表末项)不算"模态遮挡"——真机复核:弹层页 2 个"加入购物车"只判底栏那个被盖,普通页 0 误判。
|
|
@@ -462,14 +480,17 @@ if (isMain) {
|
|
|
462
480
|
const planPath = positional[0]; if (!planPath || !existsSync(planPath)) fail(3, 'run 需要 steps.json 路径');
|
|
463
481
|
let plan; try { plan = JSON.parse(readFileSync(planPath, 'utf8')); } catch (e) { fail(3, `steps.json 解析失败:${e.message}`); }
|
|
464
482
|
if (!plan.bundle || !Array.isArray(plan.steps) || !plan.steps.length) fail(3, 'steps.json 须含 bundle 与非空 steps');
|
|
483
|
+
// 上设备前先对同目录走查报告的 seen 字典核 match 目标:没出现过的多半是文案写错(状态相关文案除外),只提示不拦
|
|
484
|
+
const dict = siblingSeen(dirname(resolve(planPath)));
|
|
485
|
+
if (dict.count) { const miss = unseenTargets(plan, dict); if (miss.length) console.log(`⚠ 计划 ${miss.length} 个目标未在同目录 ${dict.count} 份走查报告的页面字典里出现过:${miss.slice(0, 8).map(m => `[${m.i}] ${m.op} ${m.match}`).join('; ')}${miss.length > 8 ? ' …' : ''}——先照字典核对再上设备(状态相关文案可忽略)`); }
|
|
465
486
|
const report = runSteps(plan, io, { stopOnFail: !flags['--continue'], label: plan.name || plan.route || planPath.replace(/^.*[\\/]/, '').replace(/\.json$/i, '') });
|
|
466
|
-
const out = flags['--out'] ||
|
|
487
|
+
const out = flags['--out'] || planPath.replace(/\.json$/i, '') + '.report.json';
|
|
467
488
|
// 同一份计划连续失败计数(同 --out 覆盖写时读上一份):≥2 即"修复循环"信号,4b 纪律按它止损
|
|
468
489
|
let prevStreak = 0; if (out && existsSync(resolve(out))) { try { prevStreak = Number(JSON.parse(readFileSync(resolve(out), 'utf8')).failStreak) || 0; } catch { prevStreak = 0; } }
|
|
469
490
|
report.failStreak = report.ok ? 0 : prevStreak + 1;
|
|
470
491
|
if (out) { mkdirSync(dirname(resolve(out)), { recursive: true }); writeFileSync(resolve(out), JSON.stringify({ plan: relative(process.cwd(), resolve(planPath)) || planPath, device: io.key, at: new Date().toISOString(), ...report }, null, 2)); }
|
|
471
492
|
for (const s of report.steps) console.log(` ${s.skipped ? '·' : s.ok ? '✓' : '✗'} [${s.i}] ${s.op} ${s.detail}${s.kind ? ` [${s.kind}]` : ''}${s.ms != null ? ` (${s.ms}ms)` : ''}${s.shot && !s.ok ? ` 现场 ${s.shot}` : ''}`);
|
|
472
|
-
if (report.failStreak >= 2) console.log(`⚠
|
|
493
|
+
if (report.failStreak >= 2) console.log(`⚠ 本计划累计第 ${report.failStreak} 次失败${report.failStreak >= 3 ? '——2 次复验已用完:记 failed --layer device 交阶段五,不再改代码、不再重跑' : '——只剩最后一次复验(每条流最多 2 次复验,不论失败在哪一步)'}`);
|
|
473
494
|
if (report.crash) { console.log(`✗ 走查期间崩溃:${report.crash.join(', ')}`); for (const c of report.crashDetail || []) console.log(` ${c.file}: ${c.message || c.reason || '?'}${c.frame ? ` @ ${c.frame}` : ''}`); process.exit(10); }
|
|
474
495
|
const bad = report.steps.filter(s => !s.ok && !s.skipped).length;
|
|
475
496
|
console.log(`[walk] ${bad ? `${bad} 步未过` : `${report.steps.length} 步全过`}${report.tapsNoChange ? `(${report.tapsNoChange} 次点击命中但页面无变化——查吞点击/接线)` : ''}${out ? ` → ${out}` : ''}`);
|
|
@@ -39,6 +39,7 @@ export const meta = {
|
|
|
39
39
|
* fallback 模型(只影响本次 runBatch,不污染全局)、退避 20s 重发;同一单元连续死亡
|
|
40
40
|
* 3 次记"无裁决失败"出队——绝不无限重发。
|
|
41
41
|
* - 末轮验收失败不再派修:修了也无人复验,"已修未验"不许记通过——直接记 failed 等 resume。
|
|
42
|
+
* - 修复者自报受阻(blocked)或未完成(done:false)的单元不复验:复验只会再判失败白耗一轮,直接记 failed,判词经 priorFile 接力。
|
|
42
43
|
* - 对抗否证法定票数:存活票 <2 先重发一次,仍不足按未过处理(高风险门不弃权)。
|
|
43
44
|
* - 出口:任何单元不收敛 → throw(run 报 FAILED,携带 resumeFrom 提示),绝不把 PARTIAL 当完成。
|
|
44
45
|
*/
|
|
@@ -175,9 +176,9 @@ for (let r = 1; r <= maxBuildRounds; r++) {
|
|
|
175
176
|
log(`构建失败(第 ${r} 轮),派修复代理`);
|
|
176
177
|
if (r === maxBuildRounds) break;
|
|
177
178
|
await agent(
|
|
178
|
-
|
|
179
|
+
`构建失败。修复以下构建错误,只准改构建输出点名的文件,禁止降级/删功能/注释掉代码来止血。\n` +
|
|
179
180
|
`必读规范:${A.conventionsFile || '(未提供)'}\n构建输出(截断):\n${(res.output || '').slice(-6000)}`,
|
|
180
|
-
opt(M.fix, { label: `fix-build-r${r}`, phase: 'Build', cache: false, skills: A.skills
|
|
181
|
+
opt(M.fix, { label: `fix-build-r${r}`, phase: 'Build', cache: false, skills: A.skills })
|
|
181
182
|
);
|
|
182
183
|
}
|
|
183
184
|
if (!built) throw new Error(`acceptance FAILED: 构建 ${maxBuildRounds} 轮未收敛。修复后用 resumeFrom 续跑。`);
|
|
@@ -264,11 +265,18 @@ for (let round = 1; round <= maxRounds && pending.length; round++) {
|
|
|
264
265
|
`需要动共享/壳文件的,不要动手,在返回值 blocked 里写清楚需要谁改什么。\n` +
|
|
265
266
|
`必读规范:${A.conventionsFile || '(未提供)'}\n单元 ${x.u.id} 的完成标准:\n${x.u.expectation}\n` +
|
|
266
267
|
`裁判判词与证据:\n${JSON.stringify(x.v.issues && x.v.issues.length ? x.v.issues : x.v.evidence).slice(0, 4000)}\n` +
|
|
267
|
-
`纪律:修真因不糊表面;禁止为过验收而降级功能;改完自查能编译;你的修复是否达标由下一轮裁判裁决,不要自评"已修复"
|
|
268
|
-
opt(M.fix, { label: `fix:${x.u.id}-r${round}`, phase: 'Fix', cache: false, schema: FIX_SCHEMA, skills: A.skills
|
|
268
|
+
`纪律:修真因不糊表面;禁止为过验收而降级功能;改完自查能编译;你的修复是否达标由下一轮裁判裁决,不要自评"已修复"。返回 done:false 或 blocked 非空 = 本轮不复验直接记 failed,只在确实没改完/越权时用。`,
|
|
269
|
+
opt(M.fix, { label: `fix:${x.u.id}-r${round}`, phase: 'Fix', cache: false, schema: FIX_SCHEMA, skills: A.skills })
|
|
269
270
|
).then(fx => ({ u: x.u, fx }))));
|
|
270
|
-
|
|
271
|
-
|
|
271
|
+
// 受阻(越权项)或自报未完成的单元不进复验:复验只会再判失败白耗一轮,直接记 failed,判词留在 lastIssues 经 priorFile 接力
|
|
272
|
+
const stuck = new Set();
|
|
273
|
+
for (const f of fixes) {
|
|
274
|
+
if (!f || !f.fx || (!f.fx.blocked && f.fx.done !== false)) continue;
|
|
275
|
+
stuck.add(f.u.id);
|
|
276
|
+
const why = f.fx.blocked ? `修复受阻(越权项):${f.fx.blocked}` : `修复者自报未完成:${String(f.fx.summary || '').slice(0, 300)}`;
|
|
277
|
+
log(`⚠ ${f.u.id} ${why}——不复验,记 failed`);
|
|
278
|
+
results.push({ id: f.u.id, status: 'failed', round, evidence: why });
|
|
279
|
+
}
|
|
272
280
|
// 修复后回构建确认没把楼修塌
|
|
273
281
|
const rb = await check(A.build.trim().slice(2));
|
|
274
282
|
if (!rb.ok) {
|
|
@@ -279,7 +287,7 @@ for (let round = 1; round <= maxRounds && pending.length; round++) {
|
|
|
279
287
|
if (!rb2.ok) throw new Error('acceptance FAILED: 修复后构建无法恢复绿灯');
|
|
280
288
|
}
|
|
281
289
|
// 复验限幅:把上轮判词带给下一轮裁判——复验只裁清单、不重开全面发现(防逐轮冒新问题的 churn)
|
|
282
|
-
pending = failed.map(x => ({ ...x.u, priorIssues: issueLines(x.v) }));
|
|
290
|
+
pending = failed.filter(x => !stuck.has(x.u.id)).map(x => ({ ...x.u, priorIssues: issueLines(x.v) }));
|
|
283
291
|
}
|
|
284
292
|
|
|
285
293
|
// ---------- 3) 高风险单元对抗否证(2/3 多数才算过) ----------
|
|
@@ -12,7 +12,7 @@ export const meta = {
|
|
|
12
12
|
* 轻编排设计(2026-09-05 重构,见 docs/design-2026-09-05-light-orchestration.md):
|
|
13
13
|
* - 只做"确定性验证":构建/selfTest/闭合,退出码裁决、重跑同答案、不靠真机、不 churn。
|
|
14
14
|
* - 不做"判断性验证":verify agent、对抗否证一律不在此——它们会翻转,逐片连续跑=churn 病根,
|
|
15
|
-
* 统一挪到末端 acceptance 模板跑一遍。故本模板只把切片推进到 ledger 的 checked
|
|
15
|
+
* 统一挪到末端 acceptance 模板跑一遍。故本模板只把切片推进到 ledger 的 checked,featureIds 只在 selfTest 绿时按 static 层过账(退出码证据),其余留给末端。
|
|
16
16
|
* - 失败即定向:确定性门红了派一次修(构建错/闭合悬空都是可行动的确定失败),仍红则记 failed 并继续,
|
|
17
17
|
* 绝不整流水 resume 重验已完成片。
|
|
18
18
|
* - 无栅栏:同层切片各自流过 实现→检查点,快的不等慢的;真依赖按拓扑分层,仅层间有序。
|
|
@@ -25,7 +25,7 @@ export const meta = {
|
|
|
25
25
|
* ledgerCmd?: "node <home>/scripts/ledger/ledger.mjs --dir <ledgerDir>"
|
|
26
26
|
* instructionsFile, conventionsFile, contextFiles?, skills?,
|
|
27
27
|
* slices: [{ id, title, targetPath (串或数组), expectation, section?, deps?: [], skills?, route?,
|
|
28
|
-
* selfTest?: "$ node spec/run-vectors.mjs" }], //
|
|
28
|
+
* selfTest?: "$ node spec/run-vectors.mjs" }], // selfTest 绿时非 on_device 断言按 static 过账,其余由末端 acceptance 过账
|
|
29
29
|
* unownedRoutes?: [{ route, reason }], // 确不迁移的注册路由(路由归属校验豁免;同时应从 route_map 删注册)
|
|
30
30
|
* smoke?: "$ node <home>/scripts/smoke/smoke.mjs --project <dst> --out <spec>/smoke",
|
|
31
31
|
* only?: [], models?: { implement?, fix? }
|
|
@@ -352,7 +352,8 @@ async function checkpoint(s, impl, finalPass = false) {
|
|
|
352
352
|
if (!fixRounds.has(key)) {
|
|
353
353
|
fixRounds.add(key);
|
|
354
354
|
log(`检查点[${s.id}]:构建红,派修复`);
|
|
355
|
-
|
|
355
|
+
const own = [...blameBuild(b.output, s).own];
|
|
356
|
+
await agent(skillLine(A.skills) + `切片 ${s.id} 后构建失败。只准改 ${[].concat(s.targetPath).join('、')} 及构建输出点名的本片文件${own.length ? `(${own.join('、')})` : ''};他片与壳/共享文件一律不动,改不了的在返回里说明。只改必要文件恢复绿灯,禁降级止血。构建输出:\n${(b.output || '').slice(-6000)}`,
|
|
356
357
|
opt(M.fix, { label: `fix-build:${s.id}`, phase: 'Checkpoint', cache: false }));
|
|
357
358
|
b = await check(cmd(A.build));
|
|
358
359
|
}
|
|
@@ -370,19 +371,25 @@ async function checkpoint(s, impl, finalPass = false) {
|
|
|
370
371
|
}
|
|
371
372
|
}
|
|
372
373
|
if (s.selfTest) {
|
|
373
|
-
|
|
374
|
+
let st = await check(cmd(s.selfTest));
|
|
374
375
|
if (!st.ok) {
|
|
375
376
|
log(`检查点[${s.id}]:自测门红,派修复`);
|
|
376
|
-
await agent(skillLine(A.skills
|
|
377
|
+
await agent(skillLine(A.skills) + `切片 ${s.id} 自测未过。按失败清单修复(只改 ${s.targetPath} 下文件):\n${(st.output || '').slice(-4000)}`,
|
|
377
378
|
opt(M.fix, { label: `fix-selftest:${s.id}`, phase: 'Checkpoint', cache: false }));
|
|
378
|
-
|
|
379
|
-
if (!
|
|
379
|
+
st = await check(cmd(s.selfTest));
|
|
380
|
+
if (!st.ok) failSlice(s.id, `切片 ${s.id} 自测门未清(resumeFrom + only:["${s.id}"])`);
|
|
381
|
+
}
|
|
382
|
+
// 自测绿是确定性证据(退出码、重跑同答案):本片 featureIds 中非 on_device 的断言按 static 层过账,
|
|
383
|
+
// 不过账则算法片的 backend 断言会一直 untested(实证一轮 44 条);末端 acceptance 仍复核,4b 的 device 结论不被 static 改写
|
|
384
|
+
if (st.ok && Array.isArray(s.featureIds) && s.featureIds.length) {
|
|
385
|
+
const ev = String(s.selfTest).replace(/^\$\s*/, '').replace(/"/g, "'").slice(0, 200);
|
|
386
|
+
await ledger(`status --type features --ids ${s.featureIds.join(',')} --to passed --skip-kind on_device --layer static --evidence "selfTest exit0:${ev}"`);
|
|
380
387
|
}
|
|
381
388
|
}
|
|
382
389
|
const c = await check(cmd(A.closureSlice));
|
|
383
390
|
if (!c.ok) {
|
|
384
391
|
log(`检查点[${s.id}]:切片级闭合有硬违规,派修复`);
|
|
385
|
-
await agent(`切片 ${s.id}
|
|
392
|
+
await agent(`切片 ${s.id} 触发闭合硬违规(跳转未注册名/资源悬空类)。只准改 ${[].concat(s.targetPath).join('、')} 下文件;按输出逐条修复:\n${(c.output || '').slice(-4000)}`,
|
|
386
393
|
opt(M.fix, { label: `fix-closure:${s.id}`, phase: 'Checkpoint', cache: false }));
|
|
387
394
|
const c2 = await check(cmd(A.closureSlice));
|
|
388
395
|
if (!c2.ok) failSlice(s.id, `切片 ${s.id} 闭合硬违规未清`);
|
|
@@ -468,14 +475,29 @@ const bad = results.filter(r => !r || r.status !== 'checked');
|
|
|
468
475
|
if (bad.length) {
|
|
469
476
|
log(`跳过集成级闭合:${bad.length} 个切片未过检查点——先收账(见收口 throw 逐片原因),修好续跑再闭合`);
|
|
470
477
|
} else {
|
|
478
|
+
// 计划里还有 planned 的切片(后续波)时不跑集成闭合:全工程扫描会把它们的骨架页当硬违规/立案报出来,修复代理就会越界去改未来波的文件
|
|
479
|
+
const pend = A.ledgerCmd ? await check(`${A.ledgerCmd} list --type slices --status planned`) : null;
|
|
480
|
+
const pendN = pend ? Number((String(pend.output || '').match(/slices (\d+) 条/) || [])[1] || 0) : 0;
|
|
481
|
+
if (pendN > 0) {
|
|
482
|
+
log(`跳过集成级闭合:计划中仍有 ${pendN} 个切片未执行(planned),留给最后一波与末端 closure-final`);
|
|
483
|
+
} else {
|
|
471
484
|
const integ = await check(cmd(A.closureIntegration));
|
|
472
485
|
if (!integ.ok) {
|
|
473
|
-
|
|
474
|
-
|
|
486
|
+
// 只喂 ✗ 硬违规行(含其缩进续行);▲ 立案留给末端验收与豁免票,修复代理不碰
|
|
487
|
+
const hard = []; let keep = false;
|
|
488
|
+
for (const line of String(integ.output || '').split('\n')) {
|
|
489
|
+
const t = line.trimStart();
|
|
490
|
+
if (t.startsWith('✗')) { keep = true; hard.push(line); continue; }
|
|
491
|
+
if (t.startsWith('▲') || t.startsWith('✓') || /^[a-z-]+: 硬违规/.test(t)) { keep = false; continue; }
|
|
492
|
+
if (keep && line.length > 0 && /^\s+/.test(line)) hard.push(line);
|
|
493
|
+
}
|
|
494
|
+
log(`集成级闭合有硬违规(✗ ${hard.filter(l => l.trimStart().startsWith('✗')).length} 条),派一次修复后复检`);
|
|
495
|
+
await agent(`集成级闭合硬违规,逐条修复。范围:只准改下列 ✗ 行点名的文件;▲ 立案不在本次范围(留末端验收与豁免票),未列出的文件一律不动;禁装机、禁操作设备。\n${hard.join('\n').slice(-6000)}`, opt(M.fix, { label: 'fix-integration', phase: 'Integration', cache: false }));
|
|
475
496
|
const integ2 = await check(cmd(A.closureIntegration));
|
|
476
497
|
if (!integ2.ok) throw new Error('mig_slices FAILED: 集成级闭合硬违规未清');
|
|
477
498
|
}
|
|
478
499
|
log('集成级闭合完成(确定性全局判定通过;判断性验收由末端 acceptance 一遍完成)');
|
|
500
|
+
}
|
|
479
501
|
}
|
|
480
502
|
|
|
481
503
|
const checked = results.filter(r => r && r.status === 'checked');
|