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
@@ -0,0 +1,420 @@
1
+ ---
2
+ name: a2h-ui-transfer
3
+ description: Batch-convert multiple Android Activity UI snapshots to HarmonyOS ArkUI (ArkTS) pages. Use when the user wants to migrate Android UI pages to HarmonyOS in bulk, port multiple Activity screens to ArkTS, or run an Android-to-HarmonyOS UI conversion across a folder of page snapshots (page_NNNN_ActivityName). Includes Lottie animation handling — greps the Lottie Animation Resources section out of the conversion report produced by a2h-resource-convert, binds each Lottie JSON to the host Activity/Fragment, and passes only the relevant entries to each per-page sub-agent so the generated ArkTS page implements the animation with @ohos/lottie (loaded from rawfile/lottie/) mirroring the Android hosting logic. Triggers on phrases like "把安卓页面迁移到鸿蒙", "Android UI 转鸿蒙", "批量转 ArkTS", "a2h-ui-transfer", or any request that supplies an Android project path + Harmony project path + a directory of page snapshots.
4
+ ---
5
+
6
+ # a2h-ui-transfer — Android → HarmonyOS UI Batch Conversion
7
+
8
+ Batch-convert a set of Android page snapshots (each snapshot is a `page_NNNN_ActivityName/` or `manual_NNNN_ActivityName/` directory containing `meta.json` + `view.xml` + optional `screenshot.png`) into HarmonyOS ArkTS pages following MVVM architecture. State management follows the target project's paradigm — **V2 for new 0→1 conversions, V1 when the existing Harmony project already uses V1** (detected per project; see Step 5).
9
+
10
+ ## Step 0 — Environment Variables Check (run first)
11
+
12
+ | Variable | config.json fallback | Needed for | Valid when |
13
+ |---|---|---|---|
14
+ | `DEVECO_HOME` *(or `DEVECO_SDK_HOME`)* | `env.DEVECO_HOME` / `env.DEVECO_SDK_HOME` | Step 6 unified build | resolves to a valid DevEco install |
15
+
16
+ If neither the env var nor `~/.hometrans/config.json` yields a valid DevEco install, **ask the user** for the DevEco Studio install path before proceeding (suggest running `ht init` to persist it as a machine environment variable).
17
+
18
+ ## Step 1 — Parse User Request
19
+
20
+ Extract these from the user's message (natural language, no fixed format):
21
+
22
+ | Variable | Meaning | Typical Phrases |
23
+ |---|---|---|
24
+ | `android_project_dir` | Android source project root | "Android project path", "安卓项目", "source project" |
25
+ | `harmony_project_dir` | HarmonyOS target project root | "target Harmony project", "鸿蒙项目", "output project" |
26
+ | `ui_info_root` | **Parent directory containing all `page_NNNN_*` or `manual_NNNN_*` subdirectories.** Optional — it is both the input location and the *output* location of Step 3's auto-capture, so when the user does not give one, default it to `${harmony_project_dir}/.hometrans/ui_info` and let Step 3 populate it from a connected device. Only when the user names a directory does it act as a pre-existing input | "page screenshots and view tree", "page snapshots", "page folder" |
27
+ | `apk_path` | Full path of the Android APK corresponding to `android_project_dir`. Passed through to `a2h-resource-convert` in Step 2, which decodes it to extract the complete merged resource set (including library resources). **Required** — that skill takes the APK as its conversion source | "APK path", "apk文件", "安卓apk" |
28
+ | `pages` (optional) | User-explicitly listed page subset | When user lists `1. .../page_0001_X` ... take these |
29
+
30
+ **`references_dir` and MVVM document directories always use skill-bundled relative paths** — **do not** accept user-provided overrides:
31
+
32
+ - mappings: `./references/mappings/`
33
+ - mvvm (V1): `./references/mvvm/`
34
+ - mvvm (V2): `./references/mvvm-v2/`
35
+
36
+ **Paradigm detection is project-level, so detect it ONCE here in the parent and pass the verdict to every sub-agent** — do not let each sub-agent re-detect it. The verdict cannot legitimately differ between pages of the same project, so per-agent re-detection is pure duplicated work (N pages → N identical scans). Run the detection rule in `./references/mvvm-v2/_范式选择说明.md` (also summarised in Phase 2.0 of `conversion-procedure.md`) against `${harmony_project_dir}` and include `PARADIGM = V1 | V2` in each sub-agent prompt (see the input list in the dispatch step). New 0→1 conversions default to V2; a project already on V1 stays on V1.
37
+
38
+ **Doc loading is lazy, by design.** Each doc set is ~10k lines of official API reference. A sub-agent reads `./references/mvvm-v2/_装饰器速查.md` (selection table + minimal syntax + error checklist for *all* V2 decorators) and opens an individual full doc only when it hits something the cheat-sheet doesn't cover. Sub-agent prompts must not instruct agents to read a whole doc directory. This is a change to *how much is loaded up front*, not to which rules apply — every decorator and mapping remains available on demand.
39
+
40
+ If a **required** path (`android_project_dir`, `harmony_project_dir`, `apk_path`) cannot be extracted from the user's message, **ask the user** — do not guess. `ui_info_root` and `pages` are optional and have the defaults described above; do not ask for them.
41
+
42
+ ## Step 2 — Resource Conversion
43
+
44
+ Invoke the `a2h-resource-convert` skill with the following parameters (all snake_case, matching that skill's declared inputs):
45
+ 1. `android_project_dir` = `android_project_dir`
46
+ 2. `harmony_project_dir` = `harmony_project_dir`
47
+ 3. `resource_mapping_path` = generate the resource mapping document under `harmony_project_dir` at `${harmony_project_dir}/resource_mapping.md`
48
+ 4. `apk_path` = `apk_path`(`a2h-resource-convert` decodes it to extract the merged resource set).
49
+ 5. `ui_module` = the Harmony UI module resolved below — pass it so that skill's `--ui-module` writes resources into the same module the pages go into.
50
+
51
+ This skill batch-converts Android resources (strings, colors, drawables, images, etc.) to HarmonyOS resource format and generates the mapping document.
52
+
53
+ **Two documents come out of that skill and they are not interchangeable — record both paths.** The **conversion report** (`<work_dir>/resource_conversion_report.md`, observed at `${harmony_project_dir}/.hometrans/resource_conversion_report.md`, ~180 KB) is aggregated and holds the **Vector Fidelity Issues** / **Lottie Animation Resources** / **Code-Defined Vector Icons** sections that Steps 4.5 and 5 need. The **mapping document** (`resource_mapping_path`) is the per-resource audit surface, **4–22 MB by design** (one observed file: 2.3 MB) and has **no Lottie section** — always `Grep <name>` it, never `Read` it whole: a truncated read drops rows that then look exactly like "that resource does not exist", and the page substitutes something that compiles.
54
+
55
+ **Resolve `<app_module>` while you are here** — the module whose Gradle file applies the `com.android.application` plugin (grep `com.android.application` across `<android_project_dir>/*/build.gradle` and `*/build.gradle.kts`). It is **usually** `app/`, but do not assume that name: many projects use `mobile/`, `client/`, `androidApp/`, or a nested path. Store it relative to `<android_project_dir>` and use it everywhere below instead of a literal `app`. If several modules apply the plugin, ask the user which one corresponds to `apk_path`. This matches the resolution `a2h-resource-convert` does in its Step 2, so the two skills agree on the module.
56
+
57
+ **Resolve `<ui_module>` here as well — the Harmony-side UI module, which is *not* always `entry`.** Read `${harmony_project_dir}/build-profile.json5` and take the `modules[]` entry whose `srcPath` holds a `module.json5` with `"type": "entry"`; store that `srcPath` relative to `harmony_project_dir`. A single-module project yields `entry`; a layered one yields a multi-segment path such as `products/phone`. **Every Harmony path from here on uses `<ui_module>`, never a literal `entry`** — pages, resources, the Step 6.0 static check, and the fidelity checker below. Getting this wrong is silent rather than loud: resources land in the module `a2h-resource-convert` resolved while pages are written to `entry/`, so every resource reference in every page dangles, and the static check — pointed at an `ets` directory that does not exist — scans zero files and reports a clean GATE. If several modules are `type: entry`, ask the user which one to convert into.
58
+
59
+ **Resource-fidelity gate before Step 5.** When that skill's report contains a **Vector Fidelity Issues** section with `error`-severity rows, fix the resources before starting per-page conversion. A drawable whose `clip-path` was dropped, or whose stroke-only path was given a solid `fill`, still has the correct file name — so every page that references it will pass Phase 5's reference checks and compile cleanly while rendering wrong. Fixing it after the pages are written means re-verifying every page that consumed it. If resources were converted in an earlier session (so no fresh report exists), you can run that skill's checker directly — it is static and needs no device:
60
+
61
+ ```
62
+ node <a2h-resource-convert-skill>/scripts/svg_fidelity_check.js \
63
+ --android-res <android_project_dir>/<app_module>/src/main/res \
64
+ --harmony-media <harmony_project_dir>/<ui_module>/src/main/resources/base/media
65
+ ```
66
+
67
+ ## Step 3 — Page Exploration
68
+
69
+ 1. Check if `ui_info_root` already contains `page_*` or `manual_*` subdirectories. If yes, validate the existing snapshots as described in Step 4, inform the user "N page snapshots already exist, skipping exploration", and skip device exploration.
70
+ 2. Otherwise, determine the target App package name (`package`). If the user didn't provide it, read `applicationId` from `<android_project_dir>/<app_module>/build.gradle` (Groovy: `applicationId "com.x"`) or `build.gradle.kts` (Kotlin DSL: `applicationId = "com.x"`), using the default/base value and ignoring flavor-specific `applicationIdSuffix`. Only if that yields nothing, fall back to the `package` attribute in `<app_module>/src/main/AndroidManifest.xml` (**deprecated since AGP 7 and absent in most current projects**, hence the fallback rather than the primary source). If still unavailable, ask the user.
71
+ 3. Execute with Bash (plain JavaScript, ESM — run directly with Node, no build step, no dependencies):
72
+ ```
73
+ node ./scripts/android_parse_fast.js --package <package_name> --output <ui_info_root_absolute_path>
74
+ ```
75
+ This script connects to the Android emulator via ADB, BFS-traverses all reachable pages in the App, and for each page saves `screenshot.png`, `view.xml`, `meta.json` into `page_NNNN_ActivityName/` subdirectories. It also generates `index.json` and `report.html` under `ui_info_root`.
76
+ 4. After execution, confirm that `index.json` has been generated under `ui_info_root`. If not, report an error and abort.
77
+
78
+ ## Step 4 — Discover Pages
79
+
80
+ If the user did not explicitly list pages: use Glob to discover both `page_*_*` and `manual_*_*` under `ui_info_root`, merge and sort them by numeric prefix.
81
+
82
+ If the user explicitly listed pages (e.g. "1. .../page_0001_MainActivity ... 19. .../page_0019_MainActivity"): use the user's list.
83
+
84
+ ### Step 4.1 — Validate exact reference inputs
85
+
86
+ For every discovered snapshot directory:
87
+
88
+ 1. Read `<snapshot>/meta.json` first.
89
+ 2. Resolve the paths declared by `screenshot`, `screenshots`, `view_xml`, and `view_xmls` relative to `ui_info_root` (or use the path as-is when absolute).
90
+ 3. **Read each resolved file by its exact path.** Glob is only for discovering snapshot directories; an empty Glob result MUST NOT be used to conclude that a path declared by `meta.json` is missing.
91
+ 4. If a path declared by `meta.json` cannot be read, mark that page `failed` with the exact missing path and stop conversion for that page. Never tell a per-page sub-agent that a screenshot/view tree is absent unless this exact-path read failed.
92
+ 5. Keep the successfully resolved paths in memory:
93
+
94
+ ```text
95
+ reference_screenshots[page_id] = [<absolute screenshot paths>]
96
+ reference_view_trees[page_id] = [<absolute view.xml paths>]
97
+ ```
98
+
99
+ Build a lightweight in-memory index keyed by Activity name. The first numerically sorted snapshot for each Activity is its `BASE_UI_INFO`; the page currently being converted is its `CURRENT_UI_INFO`. This does not change serial processing or `batch_progress.json`.
100
+
101
+ ### Step 4.2 — Group structurally isomorphic states
102
+
103
+ Several captures of the same Activity are often the *same widget tree with different data* — two `ListPreference` dialogs that differ only in option count, or the same screen before and after a locale switch. Converting those as separate passes makes the second one re-derive a structure the first already resolved, at full cost, for no new structural information.
104
+
105
+ Detect it mechanically, per Activity, before conversion:
106
+
107
+ 1. For each page, extract the **set of distinct `resource-id` values** from its merged view tree.
108
+ 2. Two pages are **isomorphic** when those sets are equal, or differ only in ids that repeat per list item (i.e. the delta is item count, not structure).
109
+ 3. Group isomorphic pages into one **state group**, ordered by page number. The lowest-numbered page is the group's primary state.
110
+
111
+ Convert each state group as **one unit of work**, with every member state's `REFERENCE_SCREENSHOTS` and `REFERENCE_VIEW_TREES` in hand together and each path labelled with the state it belongs to. Each member still gets its own `batch_progress.json` entry (same `output`, its own `summary`).
112
+
113
+ Grouping changes *how the work is batched*, not how much gets verified: the group must still satisfy Phase 5 **for every state in it**, stating per state which visible values were reproduced. This is usually *more* reliable than converting the states separately, because reproducing several states from one rule set is exactly what Phase 4.2's value-scope requirement and Phase 4.3's mock rule are trying to enforce — holding both captures at once makes it impossible to satisfy one state by silently contradicting the other.
114
+
115
+ Do **not** group when the id sets differ by genuine structure (a state that adds a panel, a toolbar, or a list where the other has none), even if the screenshots look similar.
116
+
117
+ **Emit a predicted write set per group** (needed by the Step 5 scheduler). For each group, from its Activity/Fragment role in the Android source, predict which Harmony files it will write:
118
+
119
+ | Group | Predicted writes | Class |
120
+ |---|---|---|
121
+ | the Activity's main screen | entry page + its components + shared VM/Model | host-chain |
122
+ | a panel/sheet wired to a button on that screen | its own component **+ the entry page** (to wire the trigger) | host-chain |
123
+ | a dialog reached from another panel, exposed as a reusable API | its own component + its own model | self-contained |
124
+
125
+ Then intersect the sets pairwise and mark each group `host-chain` or `self-contained`. Record it in `batch_progress.json` alongside `groups`, so a resumed run schedules the same way without recomputing. The prediction only needs to be right about *whether the set intersects another group's*, not about the exact filenames.
126
+
127
+ ### Resume from progress (checkpoint)
128
+
129
+ The batch persists progress to `${ui_info_root}/batch_progress.json` so an interrupted run can continue instead of restarting from scratch. Schema:
130
+
131
+ ```json
132
+ {
133
+ "harmony_project_dir": "<abs path>",
134
+ "updated_at": "<ISO timestamp>",
135
+ "pages": {
136
+ "page_0001_MainActivity": { "status": "done", "output": "pages/MainPage.ets", "summary": "..." },
137
+ "page_0002_LoginActivity": { "status": "failed", "error": "..." },
138
+ "page_0003_SettingsActivity": { "status": "pending" }
139
+ }
140
+ }
141
+ ```
142
+
143
+ - `status` is one of `pending` | `done` | `failed`.
144
+ - If `batch_progress.json` exists **and** its `harmony_project_dir` matches the current one, read it back and merge into the discovered page set: pages marked `done` are skipped in Step 5; `pending`/`failed` pages are (re)processed.
145
+ - If it does not exist, or `harmony_project_dir` differs (different target project), start fresh — initialize every discovered page to `pending` and write the file.
146
+ - Before skipping a `done` page, sanity-check that its recorded `output` file still exists under `harmony_project_dir`. If the output is missing, treat the page as `pending` (re-convert).
147
+
148
+ After discovery, create a task for each page via TaskCreate to track progress, initializing already-`done` pages as completed. Report to the user how many pages are being resumed vs. freshly processed (e.g. "Resuming: 7 done, 12 remaining").
149
+
150
+ ### Step 4.5 — Build the Lottie-per-page Index (silent if no Lottie)
151
+
152
+ Before dispatching sub-agents, `Grep` the Step 2 **conversion report** for `^### Lottie Animation Resources` (then `-A 60` for the table body). The section is in the report, **not** in `resource_mapping.md` — that document has no Lottie section and must never be `Read` whole (see Step 2).
153
+
154
+ - If Grep returns no match, **skip this step entirely** — do not attach any Lottie context to sub-agent prompts, and record in Step 7 that the section was searched for and absent.
155
+ - If present, parse each row of the Grep output and build an in-memory index keyed by *Suggested HarmonyOS Page*:
156
+
157
+ ```
158
+ lottie_by_page = {
159
+ "PlayerPage": [
160
+ {
161
+ source: "assets/AndroidWave.json",
162
+ target: "rawfile/lottie/androidwave.json",
163
+ load_path: "lottie/androidwave.json",
164
+ host_android: "DynamicActivity",
165
+ host_layout: "res/layout/dynamic_activity.xml",
166
+ notes: "XML lottie_fileName reference"
167
+ },
168
+ ...
169
+ ],
170
+ "Unknown": [ ... ] // Lotties whose host binding is Unknown or ambiguous
171
+ }
172
+ ```
173
+
174
+ - Match each entry's *Suggested HarmonyOS Page* to the page name a per-page sub-agent will produce (`${activity_name}` stripped of `Activity`/`Fragment` + `Page`, matching the naming rule already used by `a2h-resource-convert`). The match is case-insensitive.
175
+ - **`Unknown` and multi-host entries do NOT get auto-injected into any page.** They are collected for the final report (see Step 7).
176
+
177
+ Retain this index in memory for the whole batch; do not rewrite `resource_mapping.md`.
178
+
179
+ ### Step 4.6 — Pre-resolve the shared facts once, in the parent
180
+
181
+ Every sub-agent otherwise re-derives the same project-level facts from the same sources — N groups, N identical investigations. Resolve them **once here** and state them as settled in every prompt. This is the same reasoning that already applies to `PARADIGM` in Step 1, extended to the rest of the batch-invariant facts.
182
+
183
+ Measured effect: a group that failed after 26 min / 40 tool calls with **zero files written** (it burned the run deriving a control's font size from screenshot pixels) succeeded in 24 min on the retry, once the parent supplied the presentation mechanism, the value-selection rule, the measured bounds, and the font size as pre-resolved facts.
184
+
185
+ Resolve and pass:
186
+
187
+ | Fact | Where the parent gets it | Why it must not be per-agent |
188
+ |---|---|---|
189
+ | **Immersive mode + which layers already exist** | grep the Android sources for the edge-to-edge keywords; read the `UIAbility` | Project-level; the answer cannot differ per page. Pass the `file:line` hit or an explicit zero-hit result |
190
+ | **Lottie present at all** | Step 4.5's index (or `rawfile/` being animation-free) | One filesystem check. When absent, say nothing about Lottie so no agent spends context on it |
191
+ | **Resource-fidelity state** | Step 2's report / the checker's output | One run. Also state which mapping-doc sections are absent, so agents don't hunt for tiers that were never produced |
192
+ | **Density + app content bounds** | `scripts/measure_pack.js` (below) | Identical for every page from the same capture device |
193
+ | **Measured bounds for the group's regions** | `scripts/measure_pack.js` (below) | The parent already parses these in Step 4.1; handing over a table beats N agents re-parsing XML |
194
+ | **Ink boxes + edge colours** | `scripts/measure_pack.js` (below) | One measured run beats N agents each scripting their own pixel probes |
195
+ | **Per-region foreground/background colours** | `measure_pack.js` printed summary — the `文本取色` block | Every `Text` needs a colour, and M3's `onSurface` / `onSurfaceVariant` / `primary` / `tertiary` differ by a few dozen grey levels — indistinguishable by eye from a screenshot. It ships in the summary now, so paste it through; an agent that doesn't see it will spend a round re-probing |
196
+ | **The page's host-chain source file list** | the Activity/Composable's own imports | Reading them is unavoidable, but *discovering which ones* need not be serial. Name the files in the prompt so the agent issues one batched read instead of learning each path from the previous file's imports |
197
+ | **Conventions established by earlier groups** | the files already on disk | Decorator paradigm, naming, `fontFamily`, resource style, and any project-wide bans (e.g. zero `.position()` so far) |
198
+ | **Presentation mechanism** for a panel/dialog group | the Android fragment transaction / dialog host call site | One source read the parent does anyway to classify the group's write set in Step 4.2 |
199
+
200
+ **Run the measurement pack once per page instead of letting each agent re-derive it:**
201
+
202
+ ```
203
+ node ./scripts/measure_pack.js --ui-info <ui_info_root>/<page_id> [--density <known>]
204
+ ```
205
+
206
+ **Check `density.confident` before passing the vp numbers on as measured facts** — every vp is `px / density`, so a wrong density scales the whole page 2–3× while the build and every reference check still pass. `source: "device"` (captured `wm density`) is confident; `"inferred"` may not be; `value: null` means there were no square nodes to solve from (4 of 8 pages on one real capture), which nulls every vp in the pack. Resolve it once here with `--density` (`adb shell wm density`); if it stays low-confidence, tell the sub-agent not to cite those vp as Phase 5 evidence.
207
+
208
+ **And tell every sub-agent that `--probe` exists.** The default output answers "where is this region and how big is its ink"; it cannot answer "what colour is this text", "what are the vertex coordinates of this checkmark", or "how big is that rounded pill on a non-uniform background". Those questions come up on essentially every page, and without a bundled primitive the agent writes its own PNG decoder in `/tmp` to answer them — measured on one single-page run: **the same inflate + un-filter boilerplate re-authored 7 times**, each costing its own tool round trip, all of it duplicating the decoder already sitting in `measure_pack.js`. Pass this verbatim:
209
+
210
+ ```
211
+ node ./scripts/measure_pack.js --probe <screenshot.png> --rect x1,y1,x2,y2 \
212
+ --mode colors|runs|ascii|filter [--color RRGGBB] [--tol 14] [--density <d>]
213
+ ```
214
+
215
+ | mode | answers | use when |
216
+ |---|---|---|
217
+ | `colors` | darkest pixel + frequency histogram | an **arbitrary** rectangle's colour. Every *named region* already carries `inkColor`/`bgColor` in the default output, so reach for this mode only when the thing you need coloured is not a view-tree node (an icon's interior, one facet of a multi-tone glyph, a gradient midpoint) |
218
+ | `runs` | per-row dark pixel intervals | recovering the geometry of a line icon — a polyline's vertices are only readable from run endpoints; an ink bbox gives the outer rectangle and nothing else |
219
+ | `ascii` | region rendered as character art | seeing the shape before deciding what to measure |
220
+ | `filter` | bbox of pixels matching `--color` | the region's `ink` came back `unreliable` (`bgSpread` > 0 on a gradient/image backdrop), where "differs from background" cannot separate artwork from backdrop |
221
+
222
+ `--density` makes it print vp alongside px, so the dp arithmetic lands in the transcript rather than being redone by hand.
223
+
224
+ It parses `view.xml` via the exact paths in `meta.json`, back-solves the device density from square-node side lengths (emitting **several independent pieces of evidence**, not a bare number), normalizes every region to content-relative ratios, and — with a dependency-free PNG decoder (built-in `zlib`) — measures ink bounding boxes and edge colours. Writes `measure_pack.json` next to the snapshot; paste the summary into the prompt as `MEASUREMENT_PACK`.
225
+
226
+ Why it exists: on a measured single-page run the sub-agent spent **11 turns (~5 min) on PIL probes** deriving density, ink boxes and colours — values that are identical for every page off the same capture device and are pure arithmetic. Tool execution was 0.5% of that run's wall clock while post-result model latency was 71.3%, so the win is the eliminated *turns*, not faster measuring.
227
+
228
+ **Two honesty properties to preserve when reading its output:**
229
+ - It **only measures**. It does not decide immersive-vs-not, anchor direction, or icon tier — those need the Android source (Phase 4.2 / 4.3). Every value carries a `source` field.
230
+ - Each ink box carries `bgSpread`, and is flagged `unreliable` when the region's background is non-uniform (gradient / image / blur). On such a background "differs from background" cannot separate artwork from backdrop, **and the contamination can hit one axis while the other looks right** — measured case: a SeekBar's ink came out `x 75..1002` matching a colour-filtered probe exactly, while its height read the full declared 42px instead of the actual 26px track. So a flagged box is not usable for asserting drawn size on *either* axis; filter by the artwork's own colour instead. On a full-bleed gradient page most regions get flagged — that is the truth, not noise.
231
+
232
+ **Pre-resolving is not answer-giving, and must not become it.** Pass *facts with citations*, never conclusions about how to write the code. The sub-agent still derives structure, does its own back-substitution, and — per the sub-agent contract below — **must override any parent fact that its own source evidence contradicts**, reporting the citation. (Observed: a parent supplied a font size from platform-standard defaults; the sub-agent found a theme-level override in the app's `styles.xml`, and its arithmetic matched the reference measurement while the parent's did not. The parent then verified and adopted the correction.) A pre-resolved fact that turns out wrong should cost one agent a correction, not silently propagate.
233
+
234
+ ## Step 5 — Per-Page Conversion
235
+
236
+ consult `batch_progress.json`. If the page's `status` is `done` and its recorded `output` file still exists under `harmony_project_dir`, **skip it** and go to the next page.
237
+
238
+ The unit of work is the **state group** from Step 4.2 — a group of one page (the common case) or of several isomorphic pages. Skip a whole group only when *every* member is `done` with its `output` present; otherwise convert the group, treating the already-done members as context. For a multi-state group, items 1-4 below apply **once per member state**, each labelled with its page id, and Phase 5 evidence is required per state.
239
+
240
+ **For each page to convert**:
241
+
242
+ **MUST MUST load and read `references/conversion-procedure.md` and follow the conversion procedure**, using the following inputs when needed:
243
+
244
+ 1. The page's `ui_info` absolute path (`{ui_info_root}/page_NNNN_ActivityName`)
245
+ 2. `BASE_UI_INFO` = first snapshot directory for this Activity; `CURRENT_UI_INFO` = current page's snapshot directory
246
+ 3. `REFERENCE_SCREENSHOTS` = the exact-path-validated screenshot files for the current page (and the base page when different)
247
+ 4. `REFERENCE_VIEW_TREES` = the exact-path-validated view-tree files for the current page (and the base page when different)
248
+ 5. `android_project_dir`, `harmony_project_dir`, and `ui_module` (the Step 2 verdict — state it as settled; all Harmony paths the sub-agent writes are `{harmony_project_dir}/{ui_module}/src/main/...`)
249
+ 6. Absolute paths for mappings, mvvm (V1: `references/mvvm/`) and mvvm-v2 (V2: `references/mvvm-v2/`) directories (skill-bundled)
250
+ 6a. **`PARADIGM` = the `V1 | V2` verdict already detected by the parent.** State it as decided, so the sub-agent skips Phase 2.0 and goes straight to loading `references/mvvm-v2/_装饰器速查.md`. Also state that the doc directories are for **on-demand lookup** and must not be read wholesale — including the ~1,400-row atomic component mapping, which is searched per control/attribute rather than read end-to-end.
251
+ 7. Resource Mappings: `${harmony_project_dir}/resource_mapping.md` — tell the sub-agent to `Grep <resource-name>` it, never `Read` it (4–22 MB; see Step 2).
252
+ 8. **Lottie entries for this page (only if `lottie_by_page[suggested_page_name]` is non-empty)**. Attach the matching rows verbatim as a JSON block labelled `LOTTIE_ENTRIES_FOR_THIS_PAGE`, one object per animation. When there are no entries for this page, do NOT include the block and do NOT mention Lottie in the prompt — the sub-agent should not spend context on it. Uses these entries per `conversion-procedure.md` Phase 4.5 to wire `@ohos/lottie` into the generated ArkTS page.
253
+
254
+ The prompt MUST state that all paths in `REFERENCE_SCREENSHOTS` and `REFERENCE_VIEW_TREES` were verified by exact-path reads. It MUST NOT override them with a natural-language claim such as "there is no screenshot".
255
+
256
+ ### Sub-agent contract — include all four in every per-page prompt
257
+
258
+ **1. Write each file to disk the moment it is complete. Never batch writes to the end.**
259
+ This is not a style preference; it is measured. In a batch where transport errors killed 4 of 5 sub-agents mid-run, the agents that wrote incrementally kept all their code and only their final report was lost (cheap for the parent to reconstruct), while the agents that held finished code to write at the end lost ~25-30 minutes of correct analysis each and had to restart from zero. A sub-agent transcript may be unresumable, so unwritten work is unrecoverable. Order the writes so the least-dependent files (model, viewmodel) land first.
260
+
261
+ **2. Never write scratch, probe, or temp files anywhere under `${harmony_project_dir}/<ui_module>/src/main/resources/`.**
262
+ The resource compiler scans that directory expecting only qualifier subdirectories; any stray file fails the whole build with `Error 11211101: Failed to scan resources: invalid path ..., not a directory`. The failure surfaces at the batch build gate, long after the page that caused it was recorded `done`, so it is expensive to attribute. Scratch belongs in the system temp dir.
263
+
264
+ **3. Issue independent tool calls together in one message. Do not spend one turn per call.**
265
+ Measured on a single-page run (96 tool calls, 61.4 min): **tool execution totalled 17.9 seconds — 0.5% of wall clock**, while "model thinking + generating after a tool result" was **71.3%** (96 turns × 27.4s mean). Cost is therefore `turns × turn-latency`, and collapsing turns is the only lever that moves it — faster tools cannot help, because tools were never the cost.
266
+
267
+ Batch these (each set has no internal dependencies): all the Android sources for the page's host chain (that run read `Player.kt`, `Thumbnail.kt`, `Dimensions.kt`, `PlayerSlider.kt`, `IconButton.kt`, `CoilBitmapLoader.kt` in six separate turns); all candidate `res/drawable` XMLs; all icon-SVG content checks; `meta.json` + `view.xml` + screenshot; the per-control lookups against the atomic and interaction mapping tables.
268
+
269
+ **The criterion is dependency, not similarity.** If a call's input comes from another call's result — a path you only learn from a Glob, a file named inside a mapping row — it must wait. Batching those produces a guessed path and a wasted round trip. On that measured run, correct batching would have taken ~96 turns to ~50.
270
+
271
+ **4. A sub-agent that finds source-level evidence contradicting a parent-supplied fact MUST follow the evidence and report the override with its citation.**
272
+ Parent-supplied facts are conveniences, not authority — the parent derives them from the same sources and can be wrong. State this explicitly in the prompt so the sub-agent does not defer. (Observed: a parent specified a control's font size from platform-standard defaults; the sub-agent found a theme-level `textViewStyle` override in the app's own `styles.xml` that superseded it, and its arithmetic matched the reference measurement exactly while the parent's did not.) The parent then re-verifies the citation before accepting — see the mechanical re-verification list below.
273
+
274
+
275
+
276
+ After each page's conversion :
277
+ 1. **Update `batch_progress.json` immediately** (before moving to the next page) — set the page's `status` to `done` only when the Phase 5 evidence required by `conversion-procedure.md` is present **in the location that phase's output contract assigns to it**. Per that contract:
278
+
279
+ - **A-tier — must be written out in full in the report** (5 items): **窗口模式证据 (all four sub-items), 过渡实现证据, 装饰重复检查, 可见资源闭环, 位置锚点证据**. A one-line conclusion does not satisfy these; their failure mode is *two individually-correct rules combining into an error*, which is only self-catchable when the reasoning chain is spelled out.
280
+ - **B-tier — report carries `结论 + file:line`, the derivation lives in an `// EVIDENCE(<item>)` comment** in the `.ets`: actual screenshot/view-tree paths, normalized major-region layout evidence, shape/background mapping, and the remaining items.
281
+
282
+ Verify B-tier mechanically rather than by reading prose — the report is an index, the comments are the evidence:
283
+
284
+ ```
285
+ grep -rn "EVIDENCE(" <harmony_project_dir>/<ui_module>/src/main/ets # 全部证据位置
286
+ grep -rn "EVIDENCE(" <ets> | grep -oP "EVIDENCE\(\K[^)]+" | sort -u # 覆盖了哪些检查项
287
+ ```
288
+
289
+ Every B-tier item the report names must resolve to a matching `EVIDENCE()` tag whose name matches the Phase 5 heading **verbatim**, and the cited `file:line` must actually contain it. Otherwise set `failed` with the missing evidence.
290
+
291
+ **Why the tiers are named here explicitly.** A Phase 5 item is only actually enforced when it appears in *this* enumeration or has a rule in `scripts/arkts_static_check.js`. Moving a derivation out of the report and into a comment is only safe because this gate now checks the comment; had the enumeration kept saying "the report contains …", the gate would have gone **silently weaker** the moment the output contract changed — the exact failure this section warns about.
292
+
293
+ An item in neither place is, in practice, optional — the sub-agent can write one line about it and pass. Two observed instances: window-mode evidence was historically absent from this list and survived only because GATE `window-layer-missing` backs it; 过渡实现证据 had neither, and pages shipped with every transition marked `已降级` while still being recorded `done`. Both are why the A-tier list above is enumerated rather than left to "see Phase 5".
294
+
295
+ **Maintenance rule (mechanical, do this whenever any of the three lists changes):** cross-check the Phase 5 item headings against (a) this enumeration and (b) the rule names in `arkts_static_check.js`. Any item in neither is unprotected — either add it here or add a rule. Do not rely on the sub-agent's diligence to cover the gap. Include `output` + one-line `summary` for `done`, or `error` for `failed`, and refresh `updated_at`.
296
+ 2. Append a brief summary to a cumulative report and mark the corresponding task as completed.
297
+
298
+ **`done` requires BOTH the report's evidence AND parent-side mechanical re-verification.** The evidence list above describes what the sub-agent *claims*; these checks are cheap, independent of the sub-agent's narrative, and catch both a dead sub-agent and an honest-but-wrong one:
299
+
300
+ | Check | How |
301
+ |---|---|
302
+ | GATE is zero | run `scripts/arkts_static_check.js` yourself |
303
+ | Claimed output files exist and are complete | file exists, non-trivial length, braces balanced, no truncated tail |
304
+ | Cited `file:line` really contains the claimed fact | read that exact line — do not accept the citation on faith |
305
+ | Arithmetic back-substitution actually closes | recompute it; a claimed identity that does not evaluate is a failed page |
306
+ | Resource keys exist in every qualifier group the page needs | script the lookup across `base/` and each locale dir |
307
+ | Forbidden patterns absent | `.position(`/`.offset(`, `$r(\``, V1 decorators in a V2 project |
308
+ | **V1 only: no `@Track` class exposes a getter to the UI** | `grep -n "get [a-zA-Z]*(" ` every `@Observed` class that contains `@Track`. A getter cannot be `@Track`ed, so reading it in `build()` throws `BusinessError 140110` on the **first frame** — while the field-side completeness check passes, the build succeeds, and GATE is clean. Backstop: GATE rule `track-class-getter-in-ui` |
309
+
310
+ When a sub-agent dies after writing code but before reporting, do not re-run it blindly: verify what is on disk against this list, and reconstruct only the missing evidence. Record in `batch_progress.json` which parts of the evidence were parent-reconstructed rather than sub-agent-attested, so a later reader can tell the two apart.
311
+
312
+ ### Scheduling — serialize on the actual conflict surface, not on everything
313
+
314
+ The constraint is **write conflicts on shared files**, so serialize exactly the groups that share files and let the rest run concurrently. "Serialize everything" is the safe over-approximation, and on a real batch it is expensive: measured on one 8-page batch, a fully self-contained dialog group ran 53 min while the host chain ran 86 min; serialized that is 139 min of wall clock, scheduled it is 86 min — **the same work, the same inputs, the same per-page procedure, 38% less wall clock.**
315
+
316
+ **Classify each state group by its predicted write set** (before dispatch, from the group's target paths):
317
+
318
+ - **Host-chain group** — writes the entry page, or a shared ViewModel/Model that other groups also write (e.g. it wires a button on the host page, or extends the shared weather model). These **must run serially relative to each other**, in page order.
319
+ - **Self-contained group** — writes only its own components/models and touches no file another group writes. Typical: a dialog, a standalone overlay, a reusable widget with a clean API. These may run **concurrently** with the host chain and with each other.
320
+
321
+ **The classification is mechanically decidable, so compute it rather than guessing:** intersect each group's predicted write set with every other group's. Empty intersection ⇒ self-contained. From the same measured batch:
322
+
323
+ ```
324
+ G1 ∩ G2: WeatherHomePage / WeatherHomeViewModel / WeatherModel -> host chain
325
+ G1 ∩ G3: WeatherHomePage / WeatherHomeViewModel -> host chain
326
+ G2 ∩ G3: WeatherHomePage / WeatherHomeViewModel -> host chain
327
+ G1 ∩ G4: (empty) G2 ∩ G4: (empty) G4 ∩ G3: (empty) -> G4 self-contained
328
+ ```
329
+
330
+ The conflict surface is narrow — the entry page plus shared VM/Model. A group whose output is a dialog or an independent panel usually intersects nothing.
331
+
332
+ **Rules when running concurrently:**
333
+ 1. **The first host-chain group goes first, alone.** It creates the entry page and the shared VM/Model that later groups extend, and it establishes the conventions (decorator paradigm, naming, resource style) that every later prompt cites. Do not start anything concurrently with it.
334
+ 2. After it completes, dispatch the remaining host-chain groups **serially** and the self-contained groups **concurrently alongside them**.
335
+ 3. **A self-contained group's prompt must state that it may not edit the entry page or shared VM/Model.** If it discovers it genuinely needs to, it must report that instead of writing — the parent then does the wiring after the concurrent group completes, or reclassifies the group as host-chain on a later run. This is what keeps the static classification true at runtime.
336
+ 4. **`batch_progress.json` writes must be serialized by the parent.** Never let a sub-agent write it. Read-modify-write it yourself, one group at a time, after each group returns.
337
+ 5. If any group's actual write set turns out to violate its classification, treat the batch's parallel assumption as broken: finish serially and record it, so the next run classifies correctly.
338
+
339
+ When in doubt, serialize — a wrong parallel call costs a corrupted file and a re-run, which is worse than the time saved.
340
+
341
+ ## Step 6 — Unified Build Fix
342
+
343
+ ### Step 6.0 — Static check first (cheap, runs before the build)
344
+
345
+ Before invoking the build, run the bundled lexical checker. It is static, needs no device and no SDK, and finishes in under a second:
346
+
347
+ ```
348
+ node ./scripts/arkts_static_check.js --ets <harmony_project_dir>/<ui_module>/src/main/ets \
349
+ --resources <harmony_project_dir>/<ui_module>/src/main/resources \
350
+ --android <android_project_dir> \
351
+ --ui-info <ui_info_root>
352
+ ```
353
+
354
+ Pass `--ui-info` whenever `ui_info_root` is not the default `${harmony_project_dir}/.hometrans/ui_info` — without it the `size-mismatch-vs-measure` gate finds no `measure_pack.json` and is skipped silently. That gate is the mechanical backstop for "测归测、写归写": the agent runs `measure_pack.js`, writes the correct conversion in its trace, and then codes a Material memory value anyway (measured 48vp → `.width(24)`). Nothing else in the pipeline catches it — the build succeeds and cross-page consistency passes because every page is wrong the same way.
355
+
356
+ **The gate keys off `density.confident`** — a page whose density is unreliable (or a `measure_pack.json` predating the field) lands in `[CANDIDATES]` instead of `[GATE]`, because a wrong density makes the rule fire on nearly every literal size. Re-running `measure_pack.js` restores the `[GATE]` tier.
357
+
358
+ Pass `--android` — without it the immersive cross-check is skipped silently. It greps the Android sources for `enableEdgeToEdge` / `setDecorFitsSystemWindows` and, when the Harmony project declares neither the window layer nor the component layer, emits a `[CANDIDATES]` entry listing the call sites to reconcile. This is the one defect class that cannot be found on the Harmony side alone: an edge-to-edge page pads for insets itself, so its screenshot is **identical in shape** to a system-bar-avoiding page, and every page then gets it equally wrong so cross-page consistency passes with full marks.
359
+
360
+ It prints two sections with **different** meanings, and exits `1` only when the first is non-empty:
361
+
362
+ Three `[GATE]` rules read the *content* of referenced SVGs rather than the code, so pass `--resources` — without it they are skipped silently. They all target one failure mode: **"the resource exists" is not "the resource renders correctly."** A page that confirms icons with `ls media/` and never opens a file passes every existence check and still renders black, white, or grey boxes. See the icon-source tiering in `references/conversion-procedure.md` Phase 4.3.
363
+
364
+ One `[GATE]` rule catches a defect that survives the entire pipeline otherwise: `entry-route-not-converted`. Registering a page in `main_pages.json` only makes it *routable*; what determines the launch screen is `loadContent()` in the `UIAbility`. When those disagree, every page converts correctly, the build succeeds, reference checks pass — and launching the app shows the DevEco scaffold's Hello World, with the converted work never displayed at all. The rule fires only when the project already contains converted pages, so a not-yet-converted project is not nagged.
365
+
366
+ Two of its GATE rules exist purely to turn a **compile error** into a sub-second lexical hit, so running the checker before the first build (not after it fails) is now worth real wall clock: `state-name-shadows-attribute` — a state field named `size`/`width`/`margin`/… collides with the `CustomComponent` base method, and the compiler's `10505001` text talks about type assignability while never hinting "rename it"; and `builder-invokes-callback-param` — a `@Builder` taking a `() => void` param and invoking it bare, which is `10905204`. Measured on one page: those two classes were **6 of the 8 errors in the first build**, each build round trip costing 45-70s against under a second for the checker. Both recur across pages — `size` is the most natural name any icon component reaches for.
367
+
368
+ - **`[GATE]`** — zero-ambiguity defects. **Fix every one before building.** Doing this first is a real saving: an unterminated JSDoc block (a `*/` sequence inside a comment, e.g. from writing an evidence path like `foo_*/view.xml`) makes the compiler emit *hundreds* of errors whose line/column coordinates point into comment prose, so diagnosing it from build output means reverse-engineering phantom errors. The checker names it directly. Also gated: `$r()` template strings, `width('100%')` + horizontal margin on one element, V1/V2 paradigm mixing, `@ObservedV2`/`@Trace` used without its partner, missing window layer while pages declare `expandSafeArea`, and literal quote leakage in `element/string.json`.
369
+ - **`[CANDIDATES]`** — **not defects**, just a narrowed worklist. Whether each is correct depends on Android-side intent (anchor direction, stack order, whether that page is immersive) which lexical analysis cannot determine. Feed these into the matching Phase 5 evidence items (位置锚点证据 / 交互可达性证据 / 窗口模式证据) and annotate each with its source. **Do not "fix" a candidate just because it was listed** — a full-size container is usually legitimate.
370
+
371
+ **What it cannot do.** This checker only covers defects decidable from tokens. It cannot judge whether a value's scope was mistranslated, whether mock data reproduces the reference screenshot, or whether a claimed piece of evidence actually supports its conclusion. Those remain human Phase 5 items, and a clean `[GATE]` is **not** evidence that a page is correct.
372
+
373
+ ### Step 6.1 — Build
374
+
375
+ After every 3-5 page conversions are complete, build and compile the project until success.
376
+
377
+ ## Step 7 — Final Report
378
+
379
+ Write the cumulative report accumulated in Step 5 to `${ui_info_root}/batch_report.md`. It has three sections:
380
+
381
+ 1. **Per-page results** — one row per state group: page ids, `status`, `output` files, the one-line `summary`, and for `failed` pages the recorded `error`. Mark any page whose evidence was **parent-reconstructed** rather than sub-agent-attested (Step 5 records this in `batch_progress.json`).
382
+ 2. **Build status** — the outcome of Step 6.0's static check and Step 6.1's build, including any `[CANDIDATES]` entries that were reviewed but deliberately not "fixed", with the reason.
383
+ 3. **Manual Follow-up TODOs** — the items this skill cannot decide on its own. Every one needs a human, so list each with enough context to act on:
384
+
385
+ - **Unassigned Lottie animations** — every entry Step 4.5 collected under `lottie_by_page["Unknown"]`, plus any entry whose *Suggested HarmonyOS Page* matched more than one page (multi-host). These were deliberately **not** auto-injected into any page, so without this list they are silently dropped. Per entry, give `source`, `target`, `load_path`, `host_android`, `host_layout`, and `notes` verbatim from the index, and state what is needed: which Harmony page should host it, and the trigger that starts/stops it.
386
+ - **Degraded items (`已降级`)** — icons that could not be sourced (Phase 4.3 L3) and transitions that were declared but not implemented (Phase 5 过渡实现证据). Both are allowed to ship degraded but not to ship unmentioned.
387
+ - **Unfixed `error`-severity resource-fidelity rows** — anything the Step 2 gate flagged that was accepted rather than repaired.
388
+
389
+ When Step 4.5 found no Lottie section at all and nothing was degraded, write "无" for this section rather than omitting it — an absent section reads as "forgot to check".
390
+
391
+ ## Key Constraints
392
+
393
+ - Must Must Use The Right Resource, Find it in `harmony_project_dir`, if not exists, find it in `android_project_dir` and transfer it in time; do not invent it youself.
394
+ - If the user's `harmony_project_dir` lacks entry/resources subdirectories or the target project doesn't exist, stop and ask — do not generate code.
395
+ - If input paths are Windows paths (with backslashes), pass them as-is to sub-agents; internal Read/Glob support them.
396
+ - Screenshot/view-tree existence is determined by exact paths resolved from `meta.json`, never by Glob alone.
397
+ - This skill performs static reference-based alignment only. Step 6 remains a compile/build gate; it does not perform runtime deployment or screenshot comparison.
398
+ - **Know what the compile gate cannot catch.** A clean build with zero warnings is not evidence of visual correctness. These defect classes compile fine and pass reference-existence checks, yet render wrong:
399
+ - **Resource content errors** — a converted SVG whose `clip-path` was dropped or whose stroke-only path got a solid `fill`. The reference name is valid, so existence checks pass. Guard: the Step 2 fidelity gate above.
400
+ - **Percentage + margin overflow** — `.width('100%')` combined with left/right `margin` exceeds the parent and clips off-screen. Guard: the constraint-translation table in `references/conversion-procedure.md` Phase 4.2.
401
+ - **Window-mode (safe area) drift** — inconsistent `expandSafeArea` across pages shifts content by a status-bar height. Guard: the per-page window-mode evidence item in Phase 5.
402
+ - **Window layer missed entirely** — immersive drawing needs BOTH the window layer (`setWindowLayoutFullScreen` in the `UIAbility`) and the component layer (`expandSafeArea`). Set only the component layer and the system still reserves the status-bar strip, leaving a blank band at the top. Because every page then gets it *equally* wrong, a cross-page consistency check passes with full marks. Guard: item ② of the four-item window-mode evidence in Phase 5 — and note that consistency alone is never sufficient to pass that item.
403
+ - **Immersive mode inferred from the screenshot instead of the source** — this is how the window-mode item actually gets missed in practice, and it defeats both guards above at once. An edge-to-edge page applies inset padding itself, so its screenshot is **identical in shape** to a system-bar-avoiding page: status bar visible, content starting below it. Reading "not immersive" off that screenshot is not a judgement call, it is a judgement the screenshot cannot support. The wrong conclusion is then confirmed by "consistent with the base pages" — and the base is a non-immersive stub, so consistency points at the same wrong answer. Two steps, both apparently evidenced, both wrong. Guard: Phase 5 window-mode item ① now accepts **only** an Android source citation (`file:line` for `enableEdgeToEdge` / `setDecorFitsSystemWindows`, or an explicit zero-hit result), never a screenshot inference; consistency (item ④) is a hint, not a pass condition, and ① wins when they conflict. Mechanical backstop: CAND rule `android-immersive-not-mirrored` (requires `--android`). When ① says immersive and the base pages are not, the fix is to **add both layers to the base** (including `EntryAbility`), not to follow the base.
404
+ - **Right/bottom-anchored elements written as constant `.position()` coordinates** — the reference view tree only ever gives the absolute solution *for the capture device*, so the number is correct at the reference resolution and clips off-screen on a narrower one. Guard: the right-anchor row in the Phase 4.2 constraint table plus the **位置锚点证据** item in Phase 5.
405
+ - **Hit-test occlusion** — a full-size container (commonly used to carry a bottom/right anchor) becomes the topmost sibling in a stack and swallows the hit test for every clickable element beneath it. The elements render correctly and simply do not respond to taps; because that container has no `onClick` of its own, nothing reaches the console either. Guard: the **交互可达性证据** item in Phase 5.
406
+ - **Value-scope mistranslation** — a value the source computes *once per screen* and reuses as a constant gets translated as *computed per item* (or the reverse). The resource set and every reference name are correct, yet the selected variant is systematically wrong. Guard: the **数据驱动的可见变体 —— 必须追到调用点** requirement in Phase 4.2, plus the variant comparison added to Phase 5's 可见资源闭环.
407
+ - **Box-model semantics inverted** — `.width(24).padding(12)` faithfully mirrors an Android 24dp icon with 12dp padding, but Android measures padding *outside* the declared size (48dp total) while ArkUI measures it *inside*, leaving a 0×0 content area. The element keeps its layout slot, so every neighbour is positioned correctly and only that element is invisible. This one is not merely hard to see statically — a per-attribute review actively *confirms* it as faithful, because the mistranslation lives in the platforms' differing definitions rather than in the written value. Guard: the `padding` row in the Phase 5 常见误译速查 table — every element carrying both a size and a padding must have the padding folded into the declared size.
408
+ - **Platform default divergence** — an attribute whose default behaviour differs across platforms (e.g. sheet keyboard avoidance defaults to `TRANSLATE_AND_SCROLL`, where Android's `adjustResize` resizes instead), so the faithful mapping is to *set* the attribute explicitly rather than inherit the default. Guard: for any behaviour you rely on by default, check the SDK `.d.ts` instead of assuming parity with Android.
409
+ - **Declared transition never implemented** — `MotionScene`, `<animated-selector>`, `StateListAnimator` and animator XML live *outside* the layout, so a page can reproduce the base state perfectly and still behave nothing like the source once the user scrolls or taps. Every static check passes, because every static check only ever looks at one moment. Note the snapshots often *do* capture the post-transition state (`screenshot_scroll_n` catching a collapsed header), which makes it part of the page's acceptance scope rather than optional polish. Guard: the transition inventory in `references/conversion-procedure.md` Phase 4.2 plus the **过渡实现证据** item in Phase 5 — declaring it `已降级` is allowed, silently skipping it is not.
410
+ - **Tinting a stroke-coloured SVG with `.fillColor()`** — `fillColor` superimposes a *fill* colour only and has no effect on `stroke`. This one is produced by two individually-correct rules colliding: the fidelity rule *requires* stroke-only artwork to carry `fill="none"`, and precisely because it does, `fillColor` cannot touch it. The reference name is right, the file content is right, the code reads right, and the icon renders in its original colour (usually black). Guard: the 染色通道 sub-item in Phase 5's resource check, and GATE rule `fillcolor-on-stroke-svg` in `scripts/arkts_static_check.js`, which decides it mechanically from the SVG on disk.
411
+ - **Hand-writing an icon SVG from memory** — the target icon genuinely isn't in `res/drawable` (Compose `Icons.Outlined.*`, Phosphor and friends are `ImageVector`s compiled into a library, and Step 2 only converts `res/`), while the rules only said "no emoji, no hardcoded icons" and offered no third path. So the icon gets written out as Material's *web* SVG — which standardly carries a full-viewport `<path d="M0 0h24v24H0z" fill="none"/>` bounding box. `fillColor` superimposes onto *every* geometry and `fill="none"` does not exempt it, so the box is painted solid and covers the whole icon. Android `<vector>` sources never emit that path (verified: zero hits across 10 Android repos), which makes its presence proof the file was hand-written. Guard: GATE rule `fillcolor-on-full-viewport-path`, plus the L1/L2/L3 icon-source tiering in Phase 4.3 — an icon you cannot source must be logged as `已降级`, never invented.
412
+ - **Consuming media whose content cannot render** — `ls media/` only proves the *name* exists. Three classes compile, pass reference-existence checks, and draw nothing: an unresolved Android theme attribute (`?attr/...`) left in `fill`/`stroke`, which SVG does not understand (white box); a placeholder SVG (grey rect + resource-name text, generated during resource conversion purely to let the project compile) used as a real icon (grey box); and **an SVG that is not well-formed XML** — a duplicate attribute on one element is a *fatal* XML error, so the parser rejects the whole document and the icon draws **nothing at all** (blank, not merely wrong). That third one is different in kind from every other resource check: the others assume a valid SVG and then inspect its semantics, so none of them — nor the resource-side `svg_fidelity_check.js`, which deliberately tolerates alpha-channel differences — ever asks whether the file parses. Observed cause: the converter writes a default `fill-opacity="1"` per path and then appends the real Android `fillAlpha` as a *second* `fill-opacity` (11 of 93 SVGs in one repo). Guard: GATE rules `media-unresolved-theme-attr`, `media-placeholder-in-use`, and `media-duplicate-attribute`, all decided mechanically from file content — no need to read `resource_mapping.md`.
413
+ - **Container alignment defaults are opposite across platforms** — ArkUI `Column` defaults to `HorizontalAlign.Center`; Android `LinearLayout` and Compose `Column` default to **start**. When neither side writes an alignment attribute the two are textually identical, so a per-attribute review *confirms* it as faithful — and shrink-width children (section titles, list text) end up centred where Android has them flush left. Guard: the 文字对齐证据 item in Phase 5, plus CAND rule `container-align-unset` which lists the containers to check. Note "Android didn't write gravity" means start; it does **not** license "ArkUI doesn't write alignItems".
414
+ **The attribute that decides horizontal position is not the same one on both containers**, and conflating them defeats this guard from the inside: `Column`'s horizontal axis is its *cross* axis (`alignItems(HorizontalAlign.*)`), while `Row`'s horizontal axis is its *main* axis (`justifyContent(FlexAlign.*)`). So a `Row` carrying a perfectly correct `.alignItems(VerticalAlign.Center)` — the faithful translation of Android's `verticalAlignment` — has said nothing about left-vs-centre, yet reads as "alignment is handled" to both a reviewer and any check that greps for `alignItems`. Two individually-correct facts, one missing axis. Observed: a date-separator row centred while the container demonstrably *had* an alignment call. The CAND rule now demands the axis-appropriate attribute per container type.
415
+ - **Rebuilding a decoration the atomic component already draws** — Android exposes the internals of framework composites (`search_plate`, `search_edit_frame`, Preference internals) in the view tree, so translating them one-for-one looks maximally faithful. But ArkUI's `Search`/`TextInput`/`Radio` already draw those parts, so the element gets painted twice, and since the hand-written node's box rarely coincides with the atomic component's rounded/inset box, the surplus decoration lands *outside* the component. Guard: the part-ownership table in Phase 4.3 and the **装饰重复检查** item in Phase 5. Note this is the *opposite* direction from Phase 4.2's "expand custom/third-party components into atomic ones" — composites have an atomic equivalent, custom widgets do not.
416
+
417
+ The static backstop for all of these is the **常见误译速查** table at the end of Phase 5 — work through it before declaring a page done. For actual runtime acceptance (deploy, screenshot diff, pixel comparison), use a runtime-capable skill — in this repo, `hmos-incremental-ui-align` — after this batch completes.
418
+ - `batch_progress.json` is the single source of truth for resume. .
419
+ - The Step 6 build fix runs regardless of resume state — resuming skips per-page conversion, not the final unified build.
420
+ - **Lottie handling is silent when the resource report has no Lottie section.** Only attach Lottie entries to a sub-agent's prompt when Step 4.5's index has matching rows for that page's suggested HarmonyOS name. Never auto-inject a Lottie whose Suggested HarmonyOS Page is `Unknown` or ambiguous — those are collected for the Step 7 Manual Follow-up TODOs.