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.
Files changed (104) hide show
  1. package/README.md +1 -407
  2. package/agents/self-tester.md +376 -376
  3. package/dist/index.js +134 -116
  4. package/dist/index.js.map +3 -3
  5. package/package.json +36 -32
  6. package/skills/a2h-resource-convert/SKILL.md +902 -0
  7. package/skills/a2h-resource-convert/references/code-vector-icon-rules.md +335 -0
  8. package/skills/{hmos-resources-convert → a2h-resource-convert}/references/conversion-rules.md +15 -2
  9. package/skills/{hmos-resources-convert → a2h-resource-convert}/references/dependency-analysis-rules.md +24 -2
  10. package/skills/a2h-resource-convert/references/lottie-conversion-rules.md +219 -0
  11. package/skills/{hmos-resources-convert → a2h-resource-convert}/references/resource-mapping-rules.md +27 -2
  12. package/skills/{hmos-resources-convert → a2h-resource-convert}/references/xml-drawable-to-svg-rules.md +118 -1
  13. package/skills/a2h-resource-convert/scripts/a2h_resource_convert.js +2166 -0
  14. package/skills/a2h-resource-convert/scripts/code_vector_icons.js +607 -0
  15. package/skills/a2h-resource-convert/scripts/package.json +3 -0
  16. package/skills/a2h-resource-convert/scripts/svg_fidelity_check.js +632 -0
  17. package/skills/a2h-ui-transfer/SKILL.md +420 -0
  18. package/skills/a2h-ui-transfer/references/conversion-procedure.md +572 -0
  19. 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
  20. 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
  21. package/skills/{hmos-batch-ui-align/scripts/android_parse_fast.ts → a2h-ui-transfer/scripts/android_parse_fast.js} +397 -283
  22. package/skills/a2h-ui-transfer/scripts/arkts_static_check.js +1624 -0
  23. package/skills/a2h-ui-transfer/scripts/measure_pack.js +1005 -0
  24. package/skills/a2h-ui-transfer/scripts/package.json +3 -0
  25. package/skills/hmos-incremental-ui-align/README.md +251 -251
  26. package/skills/hmos-incremental-ui-align/SKILL.md +364 -364
  27. package/skills/hmos-incremental-ui-align/references/State_Model_Template.md +2 -2
  28. package/skills/hmos-incremental-ui-align/scripts/app_feature_verify.js +790 -0
  29. package/skills/hmos-incremental-ui-align/scripts/extract_checklist.js +285 -0
  30. package/skills/hmos-incremental-ui-align/scripts/navigation-capure.md +76 -76
  31. package/skills/hmos-incremental-ui-align/scripts/page_capture.js +756 -0
  32. package/skills/hmos-incremental-ui-align/scripts/page_capture_burst.js +155 -0
  33. package/skills/hmos-batch-ui-align/SKILL.md +0 -141
  34. package/skills/hmos-batch-ui-align/references/conversion-procedure.md +0 -217
  35. package/skills/hmos-incremental-ui-align/scripts/app_feature_verify.ts +0 -999
  36. package/skills/hmos-incremental-ui-align/scripts/extract_checklist.ts +0 -343
  37. package/skills/hmos-incremental-ui-align/scripts/page_capture.ts +0 -977
  38. package/skills/hmos-incremental-ui-align/scripts/page_capture_burst.ts +0 -188
  39. package/skills/hmos-resources-convert/SKILL.md +0 -654
  40. package/skills/hmos-resources-convert/template/AppScope/app.json5 +0 -10
  41. package/skills/hmos-resources-convert/template/AppScope/resources/base/element/string.json +0 -8
  42. package/skills/hmos-resources-convert/template/AppScope/resources/base/media/background.png +0 -0
  43. package/skills/hmos-resources-convert/template/AppScope/resources/base/media/foreground.png +0 -0
  44. package/skills/hmos-resources-convert/template/AppScope/resources/base/media/layered_image.json +0 -7
  45. package/skills/hmos-resources-convert/template/build-profile.json5 +0 -42
  46. package/skills/hmos-resources-convert/template/code-linter.json5 +0 -32
  47. package/skills/hmos-resources-convert/template/entry/build-profile.json5 +0 -33
  48. package/skills/hmos-resources-convert/template/entry/hvigorfile.ts +0 -6
  49. package/skills/hmos-resources-convert/template/entry/obfuscation-rules.txt +0 -23
  50. package/skills/hmos-resources-convert/template/entry/oh-package.json5 +0 -10
  51. package/skills/hmos-resources-convert/template/entry/src/main/ets/entryability/EntryAbility.ets +0 -48
  52. package/skills/hmos-resources-convert/template/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets +0 -16
  53. package/skills/hmos-resources-convert/template/entry/src/main/ets/pages/Index.ets +0 -23
  54. package/skills/hmos-resources-convert/template/entry/src/main/module.json5 +0 -55
  55. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/color.json +0 -8
  56. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/float.json +0 -8
  57. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/string.json +0 -16
  58. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/background.png +0 -0
  59. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/foreground.png +0 -0
  60. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/layered_image.json +0 -7
  61. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/startIcon.png +0 -0
  62. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/profile/backup_config.json +0 -3
  63. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/profile/main_pages.json +0 -5
  64. package/skills/hmos-resources-convert/template/entry/src/main/resources/dark/element/color.json +0 -8
  65. package/skills/hmos-resources-convert/template/entry/src/mock/mock-config.json5 +0 -2
  66. package/skills/hmos-resources-convert/template/entry/src/ohosTest/ets/test/Ability.test.ets +0 -35
  67. package/skills/hmos-resources-convert/template/entry/src/ohosTest/ets/test/List.test.ets +0 -5
  68. package/skills/hmos-resources-convert/template/entry/src/ohosTest/module.json5 +0 -16
  69. package/skills/hmos-resources-convert/template/entry/src/test/List.test.ets +0 -5
  70. package/skills/hmos-resources-convert/template/entry/src/test/LocalUnit.test.ets +0 -33
  71. package/skills/hmos-resources-convert/template/hvigor/hvigor-config.json5 +0 -23
  72. package/skills/hmos-resources-convert/template/hvigorfile.ts +0 -6
  73. package/skills/hmos-resources-convert/template/oh-package-lock.json5 +0 -28
  74. package/skills/hmos-resources-convert/template/oh-package.json5 +0 -10
  75. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mappings/android-to-harmonyOS-ui-atomic-component-mapping-reference.md +0 -0
  76. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mappings/android-to-harmonyOS-ui-interaction-mapping-reference.md +0 -0
  77. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mappings/android-to-harmonyOS-ui-layout-mapping-reference.md +0 -0
  78. /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
  79. /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
  80. /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
  81. /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
  82. /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
  83. /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
  84. /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
  85. /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
  86. /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
  87. /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
  88. /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
  89. /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
  90. /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
  91. /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
  92. /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
  93. /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
  94. /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
  95. /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
  96. /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
  97. /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
  98. /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
  99. /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
  100. /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
  101. /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
  102. /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
  103. /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
  104. /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());