android2harmony 0.1.4 → 0.1.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/README.md +1 -407
- package/agents/self-tester.md +376 -376
- package/dist/index.js +134 -116
- package/dist/index.js.map +3 -3
- package/package.json +36 -32
- package/skills/a2h-resource-convert/SKILL.md +902 -0
- package/skills/a2h-resource-convert/references/code-vector-icon-rules.md +335 -0
- package/skills/{hmos-resources-convert → a2h-resource-convert}/references/conversion-rules.md +15 -2
- package/skills/{hmos-resources-convert → a2h-resource-convert}/references/dependency-analysis-rules.md +24 -2
- package/skills/a2h-resource-convert/references/lottie-conversion-rules.md +219 -0
- package/skills/{hmos-resources-convert → a2h-resource-convert}/references/resource-mapping-rules.md +27 -2
- package/skills/{hmos-resources-convert → a2h-resource-convert}/references/xml-drawable-to-svg-rules.md +118 -1
- package/skills/a2h-resource-convert/scripts/a2h_resource_convert.js +2166 -0
- package/skills/a2h-resource-convert/scripts/code_vector_icons.js +607 -0
- package/skills/a2h-resource-convert/scripts/package.json +3 -0
- package/skills/a2h-resource-convert/scripts/svg_fidelity_check.js +632 -0
- package/skills/a2h-ui-transfer/SKILL.md +420 -0
- package/skills/a2h-ui-transfer/references/conversion-procedure.md +572 -0
- package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/_/350/214/203/345/274/217/351/200/211/346/213/251/350/257/264/346/230/216.md +1 -1
- package/skills/a2h-ui-transfer/references/mvvm-v2/_/350/243/205/351/245/260/345/231/250/351/200/237/346/237/245.md +133 -0
- package/skills/{hmos-batch-ui-align/scripts/android_parse_fast.ts → a2h-ui-transfer/scripts/android_parse_fast.js} +397 -283
- package/skills/a2h-ui-transfer/scripts/arkts_static_check.js +1624 -0
- package/skills/a2h-ui-transfer/scripts/measure_pack.js +1005 -0
- package/skills/a2h-ui-transfer/scripts/package.json +3 -0
- package/skills/hmos-incremental-ui-align/README.md +251 -251
- package/skills/hmos-incremental-ui-align/SKILL.md +364 -364
- package/skills/hmos-incremental-ui-align/references/State_Model_Template.md +2 -2
- package/skills/hmos-incremental-ui-align/scripts/app_feature_verify.js +790 -0
- package/skills/hmos-incremental-ui-align/scripts/extract_checklist.js +285 -0
- package/skills/hmos-incremental-ui-align/scripts/navigation-capure.md +76 -76
- package/skills/hmos-incremental-ui-align/scripts/page_capture.js +756 -0
- package/skills/hmos-incremental-ui-align/scripts/page_capture_burst.js +155 -0
- package/skills/hmos-batch-ui-align/SKILL.md +0 -141
- package/skills/hmos-batch-ui-align/references/conversion-procedure.md +0 -217
- package/skills/hmos-incremental-ui-align/scripts/app_feature_verify.ts +0 -999
- package/skills/hmos-incremental-ui-align/scripts/extract_checklist.ts +0 -343
- package/skills/hmos-incremental-ui-align/scripts/page_capture.ts +0 -977
- package/skills/hmos-incremental-ui-align/scripts/page_capture_burst.ts +0 -188
- package/skills/hmos-resources-convert/SKILL.md +0 -654
- package/skills/hmos-resources-convert/template/AppScope/app.json5 +0 -10
- package/skills/hmos-resources-convert/template/AppScope/resources/base/element/string.json +0 -8
- package/skills/hmos-resources-convert/template/AppScope/resources/base/media/background.png +0 -0
- package/skills/hmos-resources-convert/template/AppScope/resources/base/media/foreground.png +0 -0
- package/skills/hmos-resources-convert/template/AppScope/resources/base/media/layered_image.json +0 -7
- package/skills/hmos-resources-convert/template/build-profile.json5 +0 -42
- package/skills/hmos-resources-convert/template/code-linter.json5 +0 -32
- package/skills/hmos-resources-convert/template/entry/build-profile.json5 +0 -33
- package/skills/hmos-resources-convert/template/entry/hvigorfile.ts +0 -6
- package/skills/hmos-resources-convert/template/entry/obfuscation-rules.txt +0 -23
- package/skills/hmos-resources-convert/template/entry/oh-package.json5 +0 -10
- package/skills/hmos-resources-convert/template/entry/src/main/ets/entryability/EntryAbility.ets +0 -48
- package/skills/hmos-resources-convert/template/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets +0 -16
- package/skills/hmos-resources-convert/template/entry/src/main/ets/pages/Index.ets +0 -23
- package/skills/hmos-resources-convert/template/entry/src/main/module.json5 +0 -55
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/color.json +0 -8
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/float.json +0 -8
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/string.json +0 -16
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/background.png +0 -0
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/foreground.png +0 -0
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/layered_image.json +0 -7
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/startIcon.png +0 -0
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/profile/backup_config.json +0 -3
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/profile/main_pages.json +0 -5
- package/skills/hmos-resources-convert/template/entry/src/main/resources/dark/element/color.json +0 -8
- package/skills/hmos-resources-convert/template/entry/src/mock/mock-config.json5 +0 -2
- package/skills/hmos-resources-convert/template/entry/src/ohosTest/ets/test/Ability.test.ets +0 -35
- package/skills/hmos-resources-convert/template/entry/src/ohosTest/ets/test/List.test.ets +0 -5
- package/skills/hmos-resources-convert/template/entry/src/ohosTest/module.json5 +0 -16
- package/skills/hmos-resources-convert/template/entry/src/test/List.test.ets +0 -5
- package/skills/hmos-resources-convert/template/entry/src/test/LocalUnit.test.ets +0 -33
- package/skills/hmos-resources-convert/template/hvigor/hvigor-config.json5 +0 -23
- package/skills/hmos-resources-convert/template/hvigorfile.ts +0 -6
- package/skills/hmos-resources-convert/template/oh-package-lock.json5 +0 -28
- package/skills/hmos-resources-convert/template/oh-package.json5 +0 -10
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mappings/android-to-harmonyOS-ui-atomic-component-mapping-reference.md +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mappings/android-to-harmonyOS-ui-interaction-mapping-reference.md +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mappings/android-to-harmonyOS-ui-layout-mapping-reference.md +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Link/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/217/214/345/220/221/345/220/214/346/255/245.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Observed/350/243/205/351/245/260/345/231/250/345/222/214@ObjectLink/350/243/205/351/245/260/345/231/250/357/274/232/345/265/214/345/245/227/347/261/273/345/257/271/350/261/241/345/261/236/346/200/247/345/217/230/345/214/226.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Prop/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/215/225/345/220/221/345/220/214/346/255/245.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Provide/350/243/205/351/245/260/345/231/250/345/222/214@Consume/350/243/205/351/245/260/345/231/250/357/274/232/344/270/216/345/220/216/344/273/243/347/273/204/344/273/266/345/217/214/345/220/221/345/220/214/346/255/245.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@State/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/347/212/266/346/200/201.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Track/350/243/205/351/245/260/345/231/250/357/274/232class/345/257/271/350/261/241/345/261/236/346/200/247/347/272/247/346/233/264/346/226/260.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Watch/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/346/233/264/346/224/271/351/200/232/347/237/245.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/AppStorage/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200/347/232/204UI/347/212/266/346/200/201/345/255/230/345/202/250.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/Environment/357/274/232/350/256/276/345/244/207/347/216/257/345/242/203/346/237/245/350/257/242.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/LocalStorage/357/274/232/351/241/265/351/235/242/347/272/247UI/347/212/266/346/200/201/345/255/230/345/202/250.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/MVVM/346/250/241/345/274/217/357/274/210V1/357/274/211.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/PersistentStorage/357/274/232/346/214/201/344/271/205/345/214/226/345/255/230/345/202/250UI/347/212/266/346/200/201.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm//347/256/241/347/220/206/345/272/224/347/224/250/346/213/245/346/234/211/347/232/204/347/212/266/346/200/201/346/246/202/350/277/260.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/!!/350/257/255/346/263/225/357/274/232/345/217/214/345/220/221/347/273/221/345/256/232.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Computed/350/243/205/351/245/260/345/231/250/357/274/232/350/256/241/347/256/227/345/261/236/346/200/247.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Event/350/243/205/351/245/260/345/231/250/357/274/232/350/247/204/350/214/203/347/273/204/344/273/266/350/276/223/345/207/272.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Local/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/351/203/250/347/212/266/346/200/201.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Monitor/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/344/277/256/346/224/271/345/274/202/346/255/245/347/233/221/345/220/254.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@ObservedV2/350/243/205/351/245/260/345/231/250/345/222/214@Trace/350/243/205/351/245/260/345/231/250/357/274/232/347/261/273/345/261/236/346/200/247/345/217/230/345/214/226/350/247/202/346/265/213.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Once/350/243/205/351/245/260/345/231/250/357/274/232/345/210/235/345/247/213/345/214/226/345/220/214/346/255/245/344/270/200/346/254/241.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Param/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/244/226/351/203/250/350/276/223/345/205/245.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Provider/350/243/205/351/245/260/345/231/250/345/222/214@Consumer/350/243/205/351/245/260/345/231/250/357/274/232/350/267/250/347/273/204/344/273/266/345/261/202/347/272/247/345/217/214/345/220/221/345/220/214/346/255/245.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Type/350/243/205/351/245/260/345/231/250/357/274/232/346/240/207/350/256/260/347/261/273/345/261/236/346/200/247/347/232/204/347/261/273/345/236/213.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/AppStorageV2/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200UI/347/212/266/346/200/201/345/255/230/345/202/250.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/MVVM/346/250/241/345/274/217/357/274/210V2/357/274/211.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/PersistenceV2/357/274/232/346/214/201/344/271/205/345/214/226/345/255/230/345/202/250UI/347/212/266/346/200/201.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2//347/212/266/346/200/201/347/256/241/347/220/206V1/345/220/221V2/350/277/201/347/247/273/344/270/216/346/267/267/347/224/250/346/214/207/345/257/274.md" +0 -0
|
@@ -1,343 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* fix_checklist 提取工具 (TypeScript 版) —— 确定性解析 task_dir 下所有 UI_comparison.md,
|
|
4
|
-
* 逐行抽取 Diff 非空(且非"一致")的差异,生成 fix_checklist.md 草稿。
|
|
5
|
-
*
|
|
6
|
-
* 设计目标:把"凭模型肉眼枚举所有差异"降级为"机械逐行抽取 + 模型审阅去重",
|
|
7
|
-
* 从源头消除 checklist 漏项。
|
|
8
|
-
*
|
|
9
|
-
* 覆盖 UI_comparison.md 中的全部 5 类表:
|
|
10
|
-
* - 主静态组件表 (UI Component ... | Diff)
|
|
11
|
-
* - Dynamic / Transient UI Comparison
|
|
12
|
-
* - State Machine Comparison
|
|
13
|
-
* - Per-Transition Comparison
|
|
14
|
-
* - Per-State Visual Diff
|
|
15
|
-
*
|
|
16
|
-
* 用法:
|
|
17
|
-
* node extract_checklist.ts --task-dir ./tmp_xxx/task_2026xxxx
|
|
18
|
-
* node extract_checklist.ts --task-dir <dir> -o <dir>/fix_checklist.md
|
|
19
|
-
*
|
|
20
|
-
* 等价于原 extract_checklist.py(functional equivalence,非逐字节一致)。
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import * as fs from 'node:fs';
|
|
24
|
-
import * as path from 'node:path';
|
|
25
|
-
|
|
26
|
-
// Diff 单元格里这些值(小写后)视为"无差异",整行跳过
|
|
27
|
-
const NO_DIFF_VALUES = new Set([
|
|
28
|
-
'', '-', '—', '–', '一致', '相同', '无', '无差异', 'n/a', 'na', 'none',
|
|
29
|
-
'same', 'identical', 'ok', '✓', '√', '对齐', '已对齐', 'no diff', '无变化',
|
|
30
|
-
]);
|
|
31
|
-
|
|
32
|
-
// 一行 Diff 里可能并列多个差异,按这些分隔符拆成独立条目(宁可多拆,不可漏)
|
|
33
|
-
const SPLIT_PATTERN = /<br\s*\/?>|;|;|\n/;
|
|
34
|
-
|
|
35
|
-
// 占位符用于保护转义竖线 \|
|
|
36
|
-
const PIPE_PLACEHOLDER = '\x00PIPE\x00';
|
|
37
|
-
|
|
38
|
-
interface Table {
|
|
39
|
-
header: string[];
|
|
40
|
-
rows: string[][];
|
|
41
|
-
section: string;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
interface PageStats {
|
|
45
|
-
page: string;
|
|
46
|
-
items: string[];
|
|
47
|
-
tableCount: number;
|
|
48
|
-
diffRowCount: number;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** 把一行 markdown 表格拆成单元格列表(处理转义竖线)。 */
|
|
52
|
-
function splitRow(line: string): string[] {
|
|
53
|
-
let s = line.trim().replace(/\\\|/g, PIPE_PLACEHOLDER);
|
|
54
|
-
if (s.startsWith('|')) s = s.slice(1);
|
|
55
|
-
if (s.endsWith('|')) s = s.slice(0, -1);
|
|
56
|
-
return s.split('|').map((c) => c.trim().split(PIPE_PLACEHOLDER).join('|'));
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** 判断是否为 |:---|:---| 这种分隔行。 */
|
|
60
|
-
function isSeparator(cells: string[]): boolean {
|
|
61
|
-
if (cells.length === 0) return false;
|
|
62
|
-
const nonEmpty = cells.filter((c) => c.trim() !== '');
|
|
63
|
-
if (nonEmpty.length === 0) return false;
|
|
64
|
-
return cells.every((c) => c.trim() === '' || /^:?-{2,}:?$/.test(c.trim()));
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** 判断一个 Diff 单元格是否表示真实差异。 */
|
|
68
|
-
function cellHasDiff(val: string | undefined): boolean {
|
|
69
|
-
if (val === undefined || val === null) return false;
|
|
70
|
-
const v = val.trim().replace(/^[`*]+|[`*]+$/g, '').trim().toLowerCase();
|
|
71
|
-
return !NO_DIFF_VALUES.has(v) && v !== '';
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/** 在表头里找 Diff 列的索引;找不到返回 -1。 */
|
|
75
|
-
function findDiffCol(headerCells: string[]): number {
|
|
76
|
-
const candidates = ['diff', '差异', '目标', 'target'];
|
|
77
|
-
for (let i = 0; i < headerCells.length; i++) {
|
|
78
|
-
if (headerCells[i].toLowerCase().includes('diff')) return i;
|
|
79
|
-
}
|
|
80
|
-
for (let i = 0; i < headerCells.length; i++) {
|
|
81
|
-
const h = headerCells[i].toLowerCase();
|
|
82
|
-
if (candidates.some((c) => h.includes(c))) return i;
|
|
83
|
-
}
|
|
84
|
-
return -1;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/** 选一个最适合做"条目标识"的列(组件名/State/Transition/Effect/属性)。 */
|
|
88
|
-
function labelCol(headerCells: string[]): number {
|
|
89
|
-
const keys = [
|
|
90
|
-
'component', '组件', 'ui component', 'state', 'transition',
|
|
91
|
-
'effect', 'dimension', 'property', '属性', '维度', '状态',
|
|
92
|
-
];
|
|
93
|
-
for (let i = 0; i < headerCells.length; i++) {
|
|
94
|
-
const h = headerCells[i].toLowerCase();
|
|
95
|
-
if (keys.some((k) => h.includes(k))) return i;
|
|
96
|
-
}
|
|
97
|
-
return 0; // 兜底用第一列
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* 解析 markdown 文本里所有表格。返回 [{header, rows, section}]。
|
|
102
|
-
* section = 表格前最近的 ## / ### 标题(用于区分 5 类表)。
|
|
103
|
-
*/
|
|
104
|
-
function parseTables(text: string): Table[] {
|
|
105
|
-
const tables: Table[] = [];
|
|
106
|
-
const lines = text.split(/\r?\n/);
|
|
107
|
-
let currentSection = '';
|
|
108
|
-
let i = 0;
|
|
109
|
-
const n = lines.length;
|
|
110
|
-
while (i < n) {
|
|
111
|
-
const line = lines[i];
|
|
112
|
-
const h = line.trim().match(/^#{1,6}\s+(.+)$/);
|
|
113
|
-
if (h) {
|
|
114
|
-
currentSection = h[1].trim();
|
|
115
|
-
i += 1;
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
// 检测表头:当前行像表格行,下一行是分隔行
|
|
119
|
-
if (line.includes('|') && i + 1 < n && lines[i + 1].includes('|')) {
|
|
120
|
-
const header = splitRow(line);
|
|
121
|
-
const sep = splitRow(lines[i + 1]);
|
|
122
|
-
if (isSeparator(sep) && header.length >= 2) {
|
|
123
|
-
const rows: string[][] = [];
|
|
124
|
-
let j = i + 2;
|
|
125
|
-
while (j < n && lines[j].includes('|') && lines[j].trim()) {
|
|
126
|
-
const cells = splitRow(lines[j]);
|
|
127
|
-
if (isSeparator(cells)) {
|
|
128
|
-
j += 1;
|
|
129
|
-
continue;
|
|
130
|
-
}
|
|
131
|
-
rows.push(cells);
|
|
132
|
-
j += 1;
|
|
133
|
-
}
|
|
134
|
-
tables.push({ header, rows, section: currentSection });
|
|
135
|
-
i = j;
|
|
136
|
-
continue;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
i += 1;
|
|
140
|
-
}
|
|
141
|
-
return tables;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/** 根据表所在 section 给条目打标签前缀(对齐 SKILL.md 的 [STATE] 等约定)。 */
|
|
145
|
-
function classifyPrefix(section: string): string {
|
|
146
|
-
const s = section.toLowerCase();
|
|
147
|
-
if (s.includes('per-transition') || s.includes('transition')) return '[TRANSITION] ';
|
|
148
|
-
if (s.includes('per-state') || (s.includes('state') && s.includes('visual'))) return '[STATE] ';
|
|
149
|
-
if (s.includes('state machine') || section.includes('状态')) return '[STATE] ';
|
|
150
|
-
if (s.includes('dynamic') || s.includes('transient') || section.includes('动态')) return '[DYNAMIC] ';
|
|
151
|
-
return '';
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/** 从单张表抽取所有真实差异条目。 */
|
|
155
|
-
function extractItemsFromTable(table: Table): string[] {
|
|
156
|
-
const { header, rows, section } = table;
|
|
157
|
-
const diffIdx = findDiffCol(header);
|
|
158
|
-
if (diffIdx === -1) return []; // 没有 Diff 列的表(如纯枚举表)不抽
|
|
159
|
-
const lblIdx = labelCol(header);
|
|
160
|
-
const prefix = classifyPrefix(section);
|
|
161
|
-
|
|
162
|
-
const items: string[] = [];
|
|
163
|
-
for (const cells of rows) {
|
|
164
|
-
if (diffIdx >= cells.length) continue;
|
|
165
|
-
const diffVal = cells[diffIdx];
|
|
166
|
-
if (!cellHasDiff(diffVal)) continue;
|
|
167
|
-
let label = lblIdx < cells.length ? cells[lblIdx].trim() : '';
|
|
168
|
-
label = label.replace(/^[`*]+|[`*]+$/g, '').trim();
|
|
169
|
-
// 把 Diff 单元格里并列的多个差异拆开,逐条成项(防止整行塌缩漏项)
|
|
170
|
-
let parts = diffVal.split(SPLIT_PATTERN).map((p) => p.trim()).filter((p) => p);
|
|
171
|
-
if (parts.length === 0) parts = [diffVal.trim()];
|
|
172
|
-
for (const part of parts) {
|
|
173
|
-
if (!cellHasDiff(part)) continue;
|
|
174
|
-
items.push(label ? `${prefix}${label}: ${part}` : `${prefix}${part}`);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
return items;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/** 返回 {page, items, tableCount, diffRowCount}。 */
|
|
181
|
-
function processComparison(filePath: string): PageStats {
|
|
182
|
-
const text = fs.readFileSync(filePath, 'utf-8');
|
|
183
|
-
const tables = parseTables(text);
|
|
184
|
-
const items: string[] = [];
|
|
185
|
-
let diffRows = 0;
|
|
186
|
-
for (const t of tables) {
|
|
187
|
-
const diffIdx = findDiffCol(t.header);
|
|
188
|
-
if (diffIdx === -1) continue;
|
|
189
|
-
for (const cells of t.rows) {
|
|
190
|
-
if (diffIdx < cells.length && cellHasDiff(cells[diffIdx])) diffRows += 1;
|
|
191
|
-
}
|
|
192
|
-
items.push(...extractItemsFromTable(t));
|
|
193
|
-
}
|
|
194
|
-
// 页面名取自父目录名(hmos_page_{i}_{name})
|
|
195
|
-
const page = path.basename(path.dirname(filePath));
|
|
196
|
-
return { page, items, tableCount: tables.length, diffRowCount: diffRows };
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/** 递归查找所有 UI_comparison.md 文件,按路径排序。 */
|
|
200
|
-
function findComparisonFiles(taskDir: string): string[] {
|
|
201
|
-
const results: string[] = [];
|
|
202
|
-
const walk = (dir: string): void => {
|
|
203
|
-
let entries: fs.Dirent[];
|
|
204
|
-
try {
|
|
205
|
-
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
206
|
-
} catch {
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
for (const entry of entries) {
|
|
210
|
-
const full = path.join(dir, entry.name);
|
|
211
|
-
if (entry.isDirectory()) {
|
|
212
|
-
walk(full);
|
|
213
|
-
} else if (entry.isFile() && entry.name === 'UI_comparison.md') {
|
|
214
|
-
results.push(full);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
};
|
|
218
|
-
walk(taskDir);
|
|
219
|
-
return results.sort();
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/** 扫描 task_dir 下所有 UI_comparison.md,生成 checklist 文本 + 每页统计。 */
|
|
223
|
-
function buildChecklist(taskDir: string): { text: string; stats: PageStats[] } {
|
|
224
|
-
const compFiles = findComparisonFiles(taskDir);
|
|
225
|
-
const out: string[] = [
|
|
226
|
-
'# Fix Checklist',
|
|
227
|
-
'',
|
|
228
|
-
'> 本文件由 scripts/extract_checklist.ts 自动生成草稿。',
|
|
229
|
-
'> 每条对应一个 UI_comparison.md 中 Diff 非空的差异行(已逐行拆分)。',
|
|
230
|
-
'> 主 agent 需:去重、补全人类可读描述、回溯 Android 源码值,再开始修复。',
|
|
231
|
-
'',
|
|
232
|
-
];
|
|
233
|
-
const stats: PageStats[] = [];
|
|
234
|
-
if (compFiles.length === 0) {
|
|
235
|
-
out.push('_(未找到任何 UI_comparison.md —— 请先完成 Step 2)_');
|
|
236
|
-
return { text: out.join('\n'), stats };
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
for (const cf of compFiles) {
|
|
240
|
-
const info = processComparison(cf);
|
|
241
|
-
stats.push(info);
|
|
242
|
-
out.push(`## ${info.page}`);
|
|
243
|
-
out.push(
|
|
244
|
-
`<!-- source: ${cf.split(path.sep).join('/')} | tables=${info.tableCount} ` +
|
|
245
|
-
`| diff_rows=${info.diffRowCount} | items=${info.items.length} -->`,
|
|
246
|
-
);
|
|
247
|
-
if (info.items.length === 0) {
|
|
248
|
-
out.push('- _(无差异,或该页未生成有效 Diff 表 —— 请人工确认)_');
|
|
249
|
-
} else {
|
|
250
|
-
for (const it of info.items) out.push(`- [ ] ${it}`);
|
|
251
|
-
}
|
|
252
|
-
out.push('');
|
|
253
|
-
}
|
|
254
|
-
return { text: out.join('\n'), stats };
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
interface CliArgs {
|
|
258
|
-
taskDir: string;
|
|
259
|
-
output: string | null;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
function parseArgs(argv: string[]): CliArgs {
|
|
263
|
-
const args: CliArgs = { taskDir: '', output: null };
|
|
264
|
-
for (let i = 0; i < argv.length; i++) {
|
|
265
|
-
const a = argv[i];
|
|
266
|
-
const next = (): string => {
|
|
267
|
-
if (i + 1 >= argv.length) {
|
|
268
|
-
console.error(`Missing value for ${a}`);
|
|
269
|
-
process.exit(2);
|
|
270
|
-
}
|
|
271
|
-
return argv[++i];
|
|
272
|
-
};
|
|
273
|
-
if (a === '--task-dir') {
|
|
274
|
-
args.taskDir = next();
|
|
275
|
-
} else if (a === '--output' || a === '-o') {
|
|
276
|
-
args.output = next();
|
|
277
|
-
} else if (a === '-h' || a === '--help') {
|
|
278
|
-
printHelp();
|
|
279
|
-
process.exit(0);
|
|
280
|
-
} else {
|
|
281
|
-
console.error(`Unknown argument: ${a}`);
|
|
282
|
-
process.exit(2);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
if (!args.taskDir) {
|
|
286
|
-
console.error('error: --task-dir is required');
|
|
287
|
-
process.exit(2);
|
|
288
|
-
}
|
|
289
|
-
return args;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
function printHelp(): void {
|
|
293
|
-
console.log(
|
|
294
|
-
[
|
|
295
|
-
'确定性提取所有 UI_comparison.md 中的差异,生成 fix_checklist.md 草稿',
|
|
296
|
-
'',
|
|
297
|
-
'Usage: node extract_checklist.ts --task-dir <dir> [-o <output>]',
|
|
298
|
-
'',
|
|
299
|
-
' --task-dir task 目录(含 hmos_page_*/UI_comparison.md)(必填)',
|
|
300
|
-
' --output,-o 输出路径(默认 {task_dir}/fix_checklist.md)',
|
|
301
|
-
].join('\n'),
|
|
302
|
-
);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
function main(): number {
|
|
306
|
-
const args = parseArgs(process.argv.slice(2));
|
|
307
|
-
if (!fs.existsSync(args.taskDir) || !fs.statSync(args.taskDir).isDirectory()) {
|
|
308
|
-
console.log(`[ERR] task-dir 不存在: ${args.taskDir}`);
|
|
309
|
-
return 1;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
const outPath = args.output ?? path.join(args.taskDir, 'fix_checklist.md');
|
|
313
|
-
const { text, stats } = buildChecklist(args.taskDir);
|
|
314
|
-
fs.writeFileSync(outPath, text, 'utf-8');
|
|
315
|
-
|
|
316
|
-
// 对账报告:每页 diff 行数 vs 抽出条目数,便于主 agent 核对无漏
|
|
317
|
-
const totalRows = stats.reduce((sum, s) => sum + s.diffRowCount, 0);
|
|
318
|
-
const totalItems = stats.reduce((sum, s) => sum + s.items.length, 0);
|
|
319
|
-
console.log(`Wrote ${outPath}`);
|
|
320
|
-
console.log(
|
|
321
|
-
`Comparison files: ${stats.length} | total diff rows: ${totalRows} ` +
|
|
322
|
-
`| total checklist items: ${totalItems}`,
|
|
323
|
-
);
|
|
324
|
-
console.log('-'.repeat(60));
|
|
325
|
-
console.log(`${'PAGE'.padEnd(48)}${'rows'.padStart(5)}${'items'.padStart(6)}`);
|
|
326
|
-
for (const s of stats) {
|
|
327
|
-
const flag = s.items.length >= s.diffRowCount ? '' : ' <-- CHECK';
|
|
328
|
-
console.log(
|
|
329
|
-
`${s.page.slice(0, 46).padEnd(48)}${String(s.diffRowCount).padStart(5)}${String(
|
|
330
|
-
s.items.length,
|
|
331
|
-
).padStart(6)}${flag}`,
|
|
332
|
-
);
|
|
333
|
-
}
|
|
334
|
-
if (totalRows === 0) {
|
|
335
|
-
console.log(
|
|
336
|
-
'\n[WARN] 0 个 diff 行被识别。可能 comparison 表头无 Diff 列,' +
|
|
337
|
-
'或文件只有状态说明没有表格 —— 请回到 Step 2 检查。',
|
|
338
|
-
);
|
|
339
|
-
}
|
|
340
|
-
return 0;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
process.exit(main());
|