android2harmony 0.1.4 → 0.1.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.
Files changed (116) hide show
  1. package/README.md +1 -407
  2. package/agents/self-tester.md +55 -376
  3. package/dist/index.js +238 -124
  4. package/dist/index.js.map +4 -4
  5. package/package.json +36 -32
  6. package/skills/a2h-resource-convert/SKILL.md +931 -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 +2186 -0
  14. package/skills/a2h-resource-convert/scripts/app_identity.js +741 -0
  15. package/skills/a2h-resource-convert/scripts/code_vector_icons.js +607 -0
  16. package/skills/a2h-resource-convert/scripts/package.json +3 -0
  17. package/skills/a2h-resource-convert/scripts/svg_fidelity_check.js +632 -0
  18. package/skills/a2h-ui-transfer/SKILL.md +431 -0
  19. package/skills/a2h-ui-transfer/references/conversion-procedure.md +547 -0
  20. 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
  21. 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
  22. package/skills/{hmos-batch-ui-align/scripts/android_parse_fast.ts → a2h-ui-transfer/scripts/android_parse_fast.js} +532 -301
  23. package/skills/a2h-ui-transfer/scripts/arkts_static_check.js +1624 -0
  24. package/skills/a2h-ui-transfer/scripts/measure_pack.js +1005 -0
  25. package/skills/a2h-ui-transfer/scripts/package.json +3 -0
  26. package/skills/hmos-fix-build-errors/SKILL.md +1 -1
  27. package/skills/hmos-incremental-ui-align/README.md +15 -15
  28. package/skills/hmos-incremental-ui-align/SKILL.md +15 -15
  29. package/skills/hmos-incremental-ui-align/references/State_Model_Template.md +2 -2
  30. package/skills/hmos-incremental-ui-align/scripts/app_feature_verify.js +790 -0
  31. package/skills/hmos-incremental-ui-align/scripts/extract_checklist.js +285 -0
  32. package/skills/hmos-incremental-ui-align/scripts/navigation-capure.md +76 -76
  33. package/skills/hmos-incremental-ui-align/scripts/page_capture.js +756 -0
  34. package/skills/hmos-incremental-ui-align/scripts/page_capture_burst.js +155 -0
  35. package/skills/hmos-integration-test/README.md +341 -0
  36. package/skills/hmos-integration-test/SKILL.md +446 -0
  37. package/skills/hmos-integration-test/scripts/report-tool.mjs +646 -0
  38. package/skills/hmos-integration-test/scripts/resolve-metadata-tool.mjs +147 -0
  39. package/skills/hmos-integration-test/scripts/self-test-runner.mjs +1006 -0
  40. package/skills/hmos-integration-test/scripts/testcases-tool.mjs +189 -0
  41. package/skills/hmos-spec-generate/SKILL.md +26 -24
  42. package/skills/hmos-spec-generate/scripts/parse_requirements.ts +515 -0
  43. package/skills/hmos-spec-generate/template/REQ.txt +22 -0
  44. package/skills/hmos-spec-generate/template/REQ.xlsx +0 -0
  45. package/skills/hmos-batch-ui-align/SKILL.md +0 -141
  46. package/skills/hmos-batch-ui-align/references/conversion-procedure.md +0 -217
  47. package/skills/hmos-incremental-ui-align/scripts/app_feature_verify.ts +0 -999
  48. package/skills/hmos-incremental-ui-align/scripts/extract_checklist.ts +0 -343
  49. package/skills/hmos-incremental-ui-align/scripts/page_capture.ts +0 -977
  50. package/skills/hmos-incremental-ui-align/scripts/page_capture_burst.ts +0 -188
  51. package/skills/hmos-resources-convert/SKILL.md +0 -654
  52. package/skills/hmos-resources-convert/template/AppScope/app.json5 +0 -10
  53. package/skills/hmos-resources-convert/template/AppScope/resources/base/element/string.json +0 -8
  54. package/skills/hmos-resources-convert/template/AppScope/resources/base/media/background.png +0 -0
  55. package/skills/hmos-resources-convert/template/AppScope/resources/base/media/foreground.png +0 -0
  56. package/skills/hmos-resources-convert/template/AppScope/resources/base/media/layered_image.json +0 -7
  57. package/skills/hmos-resources-convert/template/build-profile.json5 +0 -42
  58. package/skills/hmos-resources-convert/template/code-linter.json5 +0 -32
  59. package/skills/hmos-resources-convert/template/entry/build-profile.json5 +0 -33
  60. package/skills/hmos-resources-convert/template/entry/hvigorfile.ts +0 -6
  61. package/skills/hmos-resources-convert/template/entry/obfuscation-rules.txt +0 -23
  62. package/skills/hmos-resources-convert/template/entry/oh-package.json5 +0 -10
  63. package/skills/hmos-resources-convert/template/entry/src/main/ets/entryability/EntryAbility.ets +0 -48
  64. package/skills/hmos-resources-convert/template/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets +0 -16
  65. package/skills/hmos-resources-convert/template/entry/src/main/ets/pages/Index.ets +0 -23
  66. package/skills/hmos-resources-convert/template/entry/src/main/module.json5 +0 -55
  67. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/color.json +0 -8
  68. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/float.json +0 -8
  69. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/string.json +0 -16
  70. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/background.png +0 -0
  71. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/foreground.png +0 -0
  72. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/layered_image.json +0 -7
  73. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/startIcon.png +0 -0
  74. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/profile/backup_config.json +0 -3
  75. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/profile/main_pages.json +0 -5
  76. package/skills/hmos-resources-convert/template/entry/src/main/resources/dark/element/color.json +0 -8
  77. package/skills/hmos-resources-convert/template/entry/src/mock/mock-config.json5 +0 -2
  78. package/skills/hmos-resources-convert/template/entry/src/ohosTest/ets/test/Ability.test.ets +0 -35
  79. package/skills/hmos-resources-convert/template/entry/src/ohosTest/ets/test/List.test.ets +0 -5
  80. package/skills/hmos-resources-convert/template/entry/src/ohosTest/module.json5 +0 -16
  81. package/skills/hmos-resources-convert/template/entry/src/test/List.test.ets +0 -5
  82. package/skills/hmos-resources-convert/template/entry/src/test/LocalUnit.test.ets +0 -33
  83. package/skills/hmos-resources-convert/template/hvigor/hvigor-config.json5 +0 -23
  84. package/skills/hmos-resources-convert/template/hvigorfile.ts +0 -6
  85. package/skills/hmos-resources-convert/template/oh-package-lock.json5 +0 -28
  86. package/skills/hmos-resources-convert/template/oh-package.json5 +0 -10
  87. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mappings/android-to-harmonyOS-ui-atomic-component-mapping-reference.md +0 -0
  88. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mappings/android-to-harmonyOS-ui-interaction-mapping-reference.md +0 -0
  89. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mappings/android-to-harmonyOS-ui-layout-mapping-reference.md +0 -0
  90. /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
  91. /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
  92. /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
  93. /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
  94. /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
  95. /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
  96. /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
  97. /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
  98. /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
  99. /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
  100. /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
  101. /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
  102. /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
  103. /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
  104. /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
  105. /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
  106. /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
  107. /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
  108. /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
  109. /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
  110. /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
  111. /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
  112. /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
  113. /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
  114. /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
  115. /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
  116. /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
@@ -0,0 +1,285 @@
1
+ #!/usr/bin/env node
2
+ import * as fs from "node:fs";
3
+ import * as path from "node:path";
4
+ const NO_DIFF_VALUES = /* @__PURE__ */ new Set([
5
+ "",
6
+ "-",
7
+ "—",
8
+ "–",
9
+ "一致",
10
+ "相同",
11
+ "无",
12
+ "无差异",
13
+ "n/a",
14
+ "na",
15
+ "none",
16
+ "same",
17
+ "identical",
18
+ "ok",
19
+ "✓",
20
+ "√",
21
+ "对齐",
22
+ "已对齐",
23
+ "no diff",
24
+ "无变化"
25
+ ]);
26
+ const SPLIT_PATTERN = /<br\s*\/?>|;|;|\n/;
27
+ const PIPE_PLACEHOLDER = "\0PIPE\0";
28
+ function splitRow(line) {
29
+ let s = line.trim().replace(/\\\|/g, PIPE_PLACEHOLDER);
30
+ if (s.startsWith("|")) s = s.slice(1);
31
+ if (s.endsWith("|")) s = s.slice(0, -1);
32
+ return s.split("|").map((c) => c.trim().split(PIPE_PLACEHOLDER).join("|"));
33
+ }
34
+ function isSeparator(cells) {
35
+ if (cells.length === 0) return false;
36
+ const nonEmpty = cells.filter((c) => c.trim() !== "");
37
+ if (nonEmpty.length === 0) return false;
38
+ return cells.every((c) => c.trim() === "" || /^:?-{2,}:?$/.test(c.trim()));
39
+ }
40
+ function cellHasDiff(val) {
41
+ if (val === void 0 || val === null) return false;
42
+ const v = val.trim().replace(/^[`*]+|[`*]+$/g, "").trim().toLowerCase();
43
+ return !NO_DIFF_VALUES.has(v) && v !== "";
44
+ }
45
+ function findDiffCol(headerCells) {
46
+ const candidates = ["diff", "差异", "目标", "target"];
47
+ for (let i = 0; i < headerCells.length; i++) {
48
+ if (headerCells[i].toLowerCase().includes("diff")) return i;
49
+ }
50
+ for (let i = 0; i < headerCells.length; i++) {
51
+ const h = headerCells[i].toLowerCase();
52
+ if (candidates.some((c) => h.includes(c))) return i;
53
+ }
54
+ return -1;
55
+ }
56
+ function labelCol(headerCells) {
57
+ const keys = [
58
+ "component",
59
+ "组件",
60
+ "ui component",
61
+ "state",
62
+ "transition",
63
+ "effect",
64
+ "dimension",
65
+ "property",
66
+ "属性",
67
+ "维度",
68
+ "状态"
69
+ ];
70
+ for (let i = 0; i < headerCells.length; i++) {
71
+ const h = headerCells[i].toLowerCase();
72
+ if (keys.some((k) => h.includes(k))) return i;
73
+ }
74
+ return 0;
75
+ }
76
+ function parseTables(text) {
77
+ const tables = [];
78
+ const lines = text.split(/\r?\n/);
79
+ let currentSection = "";
80
+ let i = 0;
81
+ const n = lines.length;
82
+ while (i < n) {
83
+ const line = lines[i];
84
+ const h = line.trim().match(/^#{1,6}\s+(.+)$/);
85
+ if (h) {
86
+ currentSection = h[1].trim();
87
+ i += 1;
88
+ continue;
89
+ }
90
+ if (line.includes("|") && i + 1 < n && lines[i + 1].includes("|")) {
91
+ const header = splitRow(line);
92
+ const sep = splitRow(lines[i + 1]);
93
+ if (isSeparator(sep) && header.length >= 2) {
94
+ const rows = [];
95
+ let j = i + 2;
96
+ while (j < n && lines[j].includes("|") && lines[j].trim()) {
97
+ const cells = splitRow(lines[j]);
98
+ if (isSeparator(cells)) {
99
+ j += 1;
100
+ continue;
101
+ }
102
+ rows.push(cells);
103
+ j += 1;
104
+ }
105
+ tables.push({ header, rows, section: currentSection });
106
+ i = j;
107
+ continue;
108
+ }
109
+ }
110
+ i += 1;
111
+ }
112
+ return tables;
113
+ }
114
+ function classifyPrefix(section) {
115
+ const s = section.toLowerCase();
116
+ if (s.includes("per-transition") || s.includes("transition")) return "[TRANSITION] ";
117
+ if (s.includes("per-state") || s.includes("state") && s.includes("visual")) return "[STATE] ";
118
+ if (s.includes("state machine") || section.includes("状态")) return "[STATE] ";
119
+ if (s.includes("dynamic") || s.includes("transient") || section.includes("动态")) return "[DYNAMIC] ";
120
+ return "";
121
+ }
122
+ function extractItemsFromTable(table) {
123
+ const { header, rows, section } = table;
124
+ const diffIdx = findDiffCol(header);
125
+ if (diffIdx === -1) return [];
126
+ const lblIdx = labelCol(header);
127
+ const prefix = classifyPrefix(section);
128
+ const items = [];
129
+ for (const cells of rows) {
130
+ if (diffIdx >= cells.length) continue;
131
+ const diffVal = cells[diffIdx];
132
+ if (!cellHasDiff(diffVal)) continue;
133
+ let label = lblIdx < cells.length ? cells[lblIdx].trim() : "";
134
+ label = label.replace(/^[`*]+|[`*]+$/g, "").trim();
135
+ let parts = diffVal.split(SPLIT_PATTERN).map((p) => p.trim()).filter((p) => p);
136
+ if (parts.length === 0) parts = [diffVal.trim()];
137
+ for (const part of parts) {
138
+ if (!cellHasDiff(part)) continue;
139
+ items.push(label ? `${prefix}${label}: ${part}` : `${prefix}${part}`);
140
+ }
141
+ }
142
+ return items;
143
+ }
144
+ function processComparison(filePath) {
145
+ const text = fs.readFileSync(filePath, "utf-8");
146
+ const tables = parseTables(text);
147
+ const items = [];
148
+ let diffRows = 0;
149
+ for (const t of tables) {
150
+ const diffIdx = findDiffCol(t.header);
151
+ if (diffIdx === -1) continue;
152
+ for (const cells of t.rows) {
153
+ if (diffIdx < cells.length && cellHasDiff(cells[diffIdx])) diffRows += 1;
154
+ }
155
+ items.push(...extractItemsFromTable(t));
156
+ }
157
+ const page = path.basename(path.dirname(filePath));
158
+ return { page, items, tableCount: tables.length, diffRowCount: diffRows };
159
+ }
160
+ function findComparisonFiles(taskDir) {
161
+ const results = [];
162
+ const walk = (dir) => {
163
+ let entries;
164
+ try {
165
+ entries = fs.readdirSync(dir, { withFileTypes: true });
166
+ } catch {
167
+ return;
168
+ }
169
+ for (const entry of entries) {
170
+ const full = path.join(dir, entry.name);
171
+ if (entry.isDirectory()) {
172
+ walk(full);
173
+ } else if (entry.isFile() && entry.name === "UI_comparison.md") {
174
+ results.push(full);
175
+ }
176
+ }
177
+ };
178
+ walk(taskDir);
179
+ return results.sort();
180
+ }
181
+ function buildChecklist(taskDir) {
182
+ const compFiles = findComparisonFiles(taskDir);
183
+ const out = [
184
+ "# Fix Checklist",
185
+ "",
186
+ "> 本文件由 scripts/extract_checklist.ts 自动生成草稿。",
187
+ "> 每条对应一个 UI_comparison.md 中 Diff 非空的差异行(已逐行拆分)。",
188
+ "> 主 agent 需:去重、补全人类可读描述、回溯 Android 源码值,再开始修复。",
189
+ ""
190
+ ];
191
+ const stats = [];
192
+ if (compFiles.length === 0) {
193
+ out.push("_(未找到任何 UI_comparison.md —— 请先完成 Step 2)_");
194
+ return { text: out.join("\n"), stats };
195
+ }
196
+ for (const cf of compFiles) {
197
+ const info = processComparison(cf);
198
+ stats.push(info);
199
+ out.push(`## ${info.page}`);
200
+ out.push(
201
+ `<!-- source: ${cf.split(path.sep).join("/")} | tables=${info.tableCount} | diff_rows=${info.diffRowCount} | items=${info.items.length} -->`
202
+ );
203
+ if (info.items.length === 0) {
204
+ out.push("- _(无差异,或该页未生成有效 Diff 表 —— 请人工确认)_");
205
+ } else {
206
+ for (const it of info.items) out.push(`- [ ] ${it}`);
207
+ }
208
+ out.push("");
209
+ }
210
+ return { text: out.join("\n"), stats };
211
+ }
212
+ function parseArgs(argv) {
213
+ const args = { taskDir: "", output: null };
214
+ for (let i = 0; i < argv.length; i++) {
215
+ const a = argv[i];
216
+ const next = () => {
217
+ if (i + 1 >= argv.length) {
218
+ console.error(`Missing value for ${a}`);
219
+ process.exit(2);
220
+ }
221
+ return argv[++i];
222
+ };
223
+ if (a === "--task-dir") {
224
+ args.taskDir = next();
225
+ } else if (a === "--output" || a === "-o") {
226
+ args.output = next();
227
+ } else if (a === "-h" || a === "--help") {
228
+ printHelp();
229
+ process.exit(0);
230
+ } else {
231
+ console.error(`Unknown argument: ${a}`);
232
+ process.exit(2);
233
+ }
234
+ }
235
+ if (!args.taskDir) {
236
+ console.error("error: --task-dir is required");
237
+ process.exit(2);
238
+ }
239
+ return args;
240
+ }
241
+ function printHelp() {
242
+ console.log(
243
+ [
244
+ "确定性提取所有 UI_comparison.md 中的差异,生成 fix_checklist.md 草稿",
245
+ "",
246
+ "Usage: node extract_checklist.ts --task-dir <dir> [-o <output>]",
247
+ "",
248
+ " --task-dir task 目录(含 hmos_page_*/UI_comparison.md)(必填)",
249
+ " --output,-o 输出路径(默认 {task_dir}/fix_checklist.md)"
250
+ ].join("\n")
251
+ );
252
+ }
253
+ function main() {
254
+ const args = parseArgs(process.argv.slice(2));
255
+ if (!fs.existsSync(args.taskDir) || !fs.statSync(args.taskDir).isDirectory()) {
256
+ console.log(`[ERR] task-dir 不存在: ${args.taskDir}`);
257
+ return 1;
258
+ }
259
+ const outPath = args.output ?? path.join(args.taskDir, "fix_checklist.md");
260
+ const { text, stats } = buildChecklist(args.taskDir);
261
+ fs.writeFileSync(outPath, text, "utf-8");
262
+ const totalRows = stats.reduce((sum, s) => sum + s.diffRowCount, 0);
263
+ const totalItems = stats.reduce((sum, s) => sum + s.items.length, 0);
264
+ console.log(`Wrote ${outPath}`);
265
+ console.log(
266
+ `Comparison files: ${stats.length} | total diff rows: ${totalRows} | total checklist items: ${totalItems}`
267
+ );
268
+ console.log("-".repeat(60));
269
+ console.log(`${"PAGE".padEnd(48)}${"rows".padStart(5)}${"items".padStart(6)}`);
270
+ for (const s of stats) {
271
+ const flag = s.items.length >= s.diffRowCount ? "" : " <-- CHECK";
272
+ console.log(
273
+ `${s.page.slice(0, 46).padEnd(48)}${String(s.diffRowCount).padStart(5)}${String(
274
+ s.items.length
275
+ ).padStart(6)}${flag}`
276
+ );
277
+ }
278
+ if (totalRows === 0) {
279
+ console.log(
280
+ "\n[WARN] 0 个 diff 行被识别。可能 comparison 表头无 Diff 列,或文件只有状态说明没有表格 —— 请回到 Step 2 检查。"
281
+ );
282
+ }
283
+ return 0;
284
+ }
285
+ process.exit(main());
@@ -1,76 +1,76 @@
1
- Create a timestamped output directory under `capture_output_dir`:
2
- ```
3
- task_dir = capture_output_dir / "task_{timestamp}"
4
- android_capture_dir = task_dir / "android_page_{i}_{name}"
5
- hmos_capture_dir = task_dir / "hmos_page_{i}_{name}"
6
- ```
7
- Each paired hmos-android page-pair should have the same `i`
8
-
9
- ## Navigation prompt rules
10
-
11
- Each page's `android_nav_path` / `hmos_nav_path` is used as the `--prompt` for `app_feature_verify.ts`.
12
- The `--prompt` mode auto-prepends "打开{app_name}," so do not include "打开{app_name}" in the prompt.
13
-
14
- ## Navigate to Android page and Capture (If navigated Success)
15
- ```bash
16
- node ./app_feature_verify.ts \
17
- --device adb \
18
- --app "{android.app_name}" \
19
- --package "{android.package}" \
20
- --prompt "{pages[i].android_nav_path}" \
21
- --max-steps 15
22
- ```
23
-
24
- **Capture Android page** (if navigation succeeded — TypeScript version, run with Node ≥ 22.18 / 23.6, no build step / no dependencies)
25
- ```bash
26
- node ./page_capture.ts --device adb -o "{android_capture_dir}"
27
- ```
28
-
29
- ## Navigate & Capture HarmonyOS Pages
30
-
31
- Same as Andorid but with `--device hdc` and HarmonyOS config values. Directory pattern:
32
- ```
33
- hmos_capture_dir = task_dir / "hmos_page_{i}_{name}"
34
- ```
35
- For pages missed in harmony OS app, navigation will certainly fail — `hmos_capture_dir` will be empty, which is expected. Skip navigation for these pages and leave the directory empty.
36
-
37
- ## Burst Capture for Multi-State Components (Step 1.5)
38
-
39
- Use `page_capture_burst.ts` to continuously capture view tree + screenshot pairs at 1-second intervals. This is designed for dynamic/stateful UI where a single snapshot is insufficient.
40
-
41
- ### Usage
42
- ```bash
43
- node ./scripts/page_capture_burst.ts --device adb -o "{output_dir}" --duration {seconds}
44
- ```
45
- - `--device`: `adb` for Android (currently the only supported device)
46
- - `-o`: output directory (each frame produces `view_NNN.xml` + `screenshot_NNN.png`)
47
- - `--duration`: capture duration in seconds (default 10, recommended 5 for most states)
48
- - `--serial`: device serial if multiple devices connected
49
-
50
- ### Workflow: Auto-Capturable States
51
- For states that can be triggered by simple gestures (tap, short long-press then release):
52
- 1. Navigate to the base page using `app_feature_verify.ts`
53
- 2. Start burst capture in background: `page_capture_burst.ts --duration 5`
54
- 3. Immediately trigger the state change via `app_feature_verify.ts` with the action prompt
55
- 4. From burst output, select the frame that best represents each state
56
-
57
- ### Workflow: Manual-Capture States
58
- For states requiring continuous gestures (long-press hold, drag) or physical input (voice, sensor):
59
- 1. Navigate to the base page using `app_feature_verify.ts`
60
- 2. Print instructions for the user to manually perform the gesture
61
- 3. User runs burst capture in terminal while performing the gesture on device
62
- 4. User confirms capture is complete
63
-
64
- ### Output Directory Naming for State Captures
65
- ```
66
- {platform}_page_{i}_{base_name}_state_{state_id}_{state_name}/
67
- screenshot_001.png
68
- view_001.xml
69
- state_context.md (extracted from state_model.md)
70
- ```
71
-
72
- ### Selecting Representative Frames
73
- After burst capture, compare consecutive frames by diffing their view tree XML:
74
- - If `view_NNN.xml` differs significantly from `view_NNN-1.xml` (new nodes, visibility changes, text changes), it likely represents a state transition
75
- - Select the first stable frame after a transition as the representative for that state
76
-
1
+ Create a timestamped output directory under `capture_output_dir`:
2
+ ```
3
+ task_dir = capture_output_dir / "task_{timestamp}"
4
+ android_capture_dir = task_dir / "android_page_{i}_{name}"
5
+ hmos_capture_dir = task_dir / "hmos_page_{i}_{name}"
6
+ ```
7
+ Each paired hmos-android page-pair should have the same `i`
8
+
9
+ ## Navigation prompt rules
10
+
11
+ Each page's `android_nav_path` / `hmos_nav_path` is used as the `--prompt` for `app_feature_verify.js`.
12
+ The `--prompt` mode auto-prepends "打开{app_name}," so do not include "打开{app_name}" in the prompt.
13
+
14
+ ## Navigate to Android page and Capture (If navigated Success)
15
+ ```bash
16
+ node ./app_feature_verify.js \
17
+ --device adb \
18
+ --app "{android.app_name}" \
19
+ --package "{android.package}" \
20
+ --prompt "{pages[i].android_nav_path}" \
21
+ --max-steps 15
22
+ ```
23
+
24
+ **Capture Android page** (if navigation succeeded — TypeScript version, run with Node ≥ 22.18 / 23.6, no build step / no dependencies)
25
+ ```bash
26
+ node ./page_capture.js --device adb -o "{android_capture_dir}"
27
+ ```
28
+
29
+ ## Navigate & Capture HarmonyOS Pages
30
+
31
+ Same as Andorid but with `--device hdc` and HarmonyOS config values. Directory pattern:
32
+ ```
33
+ hmos_capture_dir = task_dir / "hmos_page_{i}_{name}"
34
+ ```
35
+ For pages missed in harmony OS app, navigation will certainly fail — `hmos_capture_dir` will be empty, which is expected. Skip navigation for these pages and leave the directory empty.
36
+
37
+ ## Burst Capture for Multi-State Components (Step 1.5)
38
+
39
+ Use `page_capture_burst.js` to continuously capture view tree + screenshot pairs at 1-second intervals. This is designed for dynamic/stateful UI where a single snapshot is insufficient.
40
+
41
+ ### Usage
42
+ ```bash
43
+ node ./scripts/page_capture_burst.js --device adb -o "{output_dir}" --duration {seconds}
44
+ ```
45
+ - `--device`: `adb` for Android (currently the only supported device)
46
+ - `-o`: output directory (each frame produces `view_NNN.xml` + `screenshot_NNN.png`)
47
+ - `--duration`: capture duration in seconds (default 10, recommended 5 for most states)
48
+ - `--serial`: device serial if multiple devices connected
49
+
50
+ ### Workflow: Auto-Capturable States
51
+ For states that can be triggered by simple gestures (tap, short long-press then release):
52
+ 1. Navigate to the base page using `app_feature_verify.js`
53
+ 2. Start burst capture in background: `page_capture_burst.js --duration 5`
54
+ 3. Immediately trigger the state change via `app_feature_verify.js` with the action prompt
55
+ 4. From burst output, select the frame that best represents each state
56
+
57
+ ### Workflow: Manual-Capture States
58
+ For states requiring continuous gestures (long-press hold, drag) or physical input (voice, sensor):
59
+ 1. Navigate to the base page using `app_feature_verify.js`
60
+ 2. Print instructions for the user to manually perform the gesture
61
+ 3. User runs burst capture in terminal while performing the gesture on device
62
+ 4. User confirms capture is complete
63
+
64
+ ### Output Directory Naming for State Captures
65
+ ```
66
+ {platform}_page_{i}_{base_name}_state_{state_id}_{state_name}/
67
+ screenshot_001.png
68
+ view_001.xml
69
+ state_context.md (extracted from state_model.md)
70
+ ```
71
+
72
+ ### Selecting Representative Frames
73
+ After burst capture, compare consecutive frames by diffing their view tree XML:
74
+ - If `view_NNN.xml` differs significantly from `view_NNN-1.xml` (new nodes, visibility changes, text changes), it likely represents a state transition
75
+ - Select the first stable frame after a transition as the representative for that state
76
+