android2harmony 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "android2harmony",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "HomeTrans Plugin for DevEco Code — standalone plugin containing A2H skills, agents, and tools for converting Android apps to HarmonyOS",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,6 +1,6 @@
1
1
  ---
2
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.
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. 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
4
  ---
5
5
 
6
6
  # a2h-ui-transfer — Android → HarmonyOS UI Batch Conversion
@@ -39,7 +39,7 @@ Extract these from the user's message (natural language, no fixed format):
39
39
 
40
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
41
 
42
- ## Step 2 — Resource Conversion
42
+ ## Step 2 — Resource Conversion (use a sub-agent, type: general)
43
43
 
44
44
  Invoke the `a2h-resource-convert` skill with the following parameters (all snake_case, matching that skill's declared inputs):
45
45
  1. `android_project_dir` = `android_project_dir`
@@ -50,11 +50,11 @@ Invoke the `a2h-resource-convert` skill with the following parameters (all snake
50
50
 
51
51
  This skill batch-converts Android resources (strings, colors, drawables, images, etc.) to HarmonyOS resource format and generates the mapping document.
52
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.
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`,) 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** — 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
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.
55
+ **Resolve `<app_module>` ** — 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
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.
57
+ **Resolve `<ui_module>` — 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
58
 
59
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
60
 
@@ -245,7 +245,7 @@ Why it exists: on a measured single-page run the sub-agent spent **11 turns (~5
245
245
 
246
246
  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.
247
247
 
248
- **For each page to convert**:
248
+ **Invoke a sub-agent(type:general) for each page to convert**:
249
249
 
250
250
  **The per-page conversion procedure lives in `./references/conversion-procedure.md`, and the sub-agent is the one that reads it** — the parent passes its absolute path (input 0 below).
251
251
 
@@ -317,6 +317,7 @@ After each page's conversion :
317
317
  | Resource keys exist in every qualifier group the page needs | script the lookup across `base/` and each locale dir |
318
318
  | Forbidden patterns absent | `.position(`/`.offset(`, `$r(\``, V1 decorators in a V2 project |
319
319
  | **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` |
320
+ | **Self-contained component wired into its host** | discharged at Step 5.5 (batch-level, not per-page): the parent imports each component and binds its trigger; a page's `done` during the batch is provisional for its triggers until Step 5.5 completes — GATE `orphan-component` zero is the batch-level discharge |
320
321
 
321
322
  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.
322
323
 
@@ -343,12 +344,20 @@ The conflict surface is narrow — the entry page plus shared VM/Model. A group
343
344
  **Rules when running concurrently:**
344
345
  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.
345
346
  2. After it completes, dispatch the remaining host-chain groups **serially** and the self-contained groups **concurrently alongside them**.
346
- 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.
347
+ 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 that wiring in **Step 5.5**, or reclassifies the group as host-chain on a later run. This is what keeps the static classification true at runtime.
347
348
  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.
348
349
  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.
349
350
 
350
351
  When in doubt, serialize — a wrong parallel call costs a corrupted file and a re-run, which is worse than the time saved.
351
352
 
353
+ ### Step 5.5 — Parent Wiring (after all groups return, before Step 6)
354
+
355
+ Self-contained groups write only their own component files — Scheduling rule 3 forbids their sub-agents from editing the entry page or shared VM/Model. That makes wiring each component into its host **the parent's job, and no one else's**: import the component in the host page, instantiate it, add a `@Local` show flag, and bind the trigger (the button / icon / menu entry that opens it) to toggle that flag; navigation-bearing triggers get `router.pushUrl`. A trigger that still calls a placeholder ViewModel method is unwired.
356
+
357
+ This was previously a subordinate clause in Scheduling rule 3, protected by no gate — and the failure is silent: every component file legal, the build green, resource checks green, while every trigger on the host page does nothing (observed: 11 components converted, 0 responsive icons). Hence the numbered step.
358
+
359
+ **Mechanical gate:** run `scripts/arkts_static_check.js`; GATE `orphan-component` (an exported struct that no file imports and no file instantiates can never render) must be **zero** before Step 6 begins. Wire components until it is.
360
+
352
361
  ## Step 6 — Unified Build Fix
353
362
 
354
363
  ### Step 6.0 — Static check first (cheap, runs before the build)
@@ -374,6 +383,8 @@ Three `[GATE]` rules read the *content* of referenced SVGs rather than the code,
374
383
 
375
384
  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.
376
385
 
386
+ Another `[GATE]` rule closes the assembly gap that `entry-route-not-converted` cannot see: `orphan-component`. A self-contained dialog or sheet component is written by a sub-agent forbidden to touch the host page; until the parent wires it in (Step 5.5), it is a legal file that nothing can ever render — every other check green, every trigger dead. The rule fires when an exported struct appears in no import and no instantiation, so an unwired batch cannot reach the build.
387
+
377
388
  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.
378
389
 
379
390
  - **`[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`.
@@ -31,6 +31,14 @@
31
31
  * 检查全过,唯一症状是启动后停在脚手架的 Hello World,转换成果一次也没显示过 ——
32
32
  * 只有真机启动能看见,但判据完全是词法的,故定为 GATE。
33
33
  * 它以「工程内已有转换产出页」为前提:空工程加载脚手架页是正确状态,不报。
34
+ *
35
+ * R13(orphan-component)针对第三个同源模式:**「写出来了」不等于「接上去了」**。
36
+ * 调度器把对话框/面板组判为 self-contained 时,子代理只写自己的组件文件、被禁止
37
+ * 改宿主页,接线责任落在父代理「并发组完成后的统一接线」上 —— 而那一步没有任何
38
+ * 门禁保护。一旦父级跳过:每个组件文件都合法、编译通过、资源闭环全绿,宿主页的
39
+ * 触发按钮却什么都不做,唯一症状是真机上「图标点了没反应」。判据完全词法:
40
+ * 非 @Entry 文件里 export struct 的名字,未出现在任何 import 中、也未在本文件内
41
+ * 实例化 —— 该组件永远不可能被渲染。同为 GATE。
34
42
  */
35
43
 
36
44
  import * as fs from 'fs';
@@ -1302,6 +1310,77 @@ function checkEntryRoute(scans, etsDir) {
1302
1310
  }
1303
1311
  }
1304
1312
 
1313
+ /**
1314
+ * GATE R13:孤儿组件 —— 已导出的自定义组件从未被 import,也从未在本文件内实例化。
1315
+ *
1316
+ * 失效模式(实测):批调度把对话框/底部弹窗/菜单组判为 self-contained,每个子代理
1317
+ * 只写自己的 `components/Xxx.ets` 且被禁止改宿主页;接线是父代理在并发组完成后的
1318
+ * 职责,但该步骤无门禁。父级一旦跳过,产出「每个零件都合格、但没装配」的工程:
1319
+ * 组件文件合法、编译通过、资源引用闭环全绿 —— 宿主页触发按钮调用的还是占位逻辑,
1320
+ * 症状只有真机上「点了没反应」。和 R11 同源:编译门禁抓不到、判据却纯词法。
1321
+ *
1322
+ * 判据(全部词法,零歧义):
1323
+ * 文件不含 @Entry(经 main_pages.json 路由加载的页不需要被 import);
1324
+ * 且其中 `export struct X` 的名字 X:
1325
+ * (1) 未出现在任何文件的 import 语句中(ArkTS 里 struct 非全局,不 import 就用不到);
1326
+ * (2) 也未在本文件内实例化(排除「导出仅供测试、本文件自用」的合法形态)。
1327
+ *
1328
+ * 豁免与边界:
1329
+ * - @Entry 文件整体豁免:入口 struct 靠路由加载,同文件的辅助 struct 通常由入口消费;
1330
+ * - 别名导入(`import { X as Y }`)按消费 X 计(取 as 之前的名字);
1331
+ * - `import type` 不算消费 —— 类型导入无法让组件渲染;
1332
+ * - 组件被另一个孤儿组件 import 时不报被引用方,只报顶层孤儿(责任指向可操作的那一层);
1333
+ * - 前置条件:工程内至少存在一个 @Entry 文件 —— 纯库模块(无页面)不适用本判据。
1334
+ */
1335
+ function checkOrphanComponents(scans) {
1336
+ if (!scans.some((s) => /@Entry\b/.test(s.text))) {
1337
+ return;
1338
+ }
1339
+
1340
+ // 1. 汇总所有 import 进来的名字(跨文件消费面)
1341
+ const namedImportRe = /import\s+(?:[A-Za-z_$][\w$]*\s*,\s*)?\{([^}]*)\}\s*from/g;
1342
+ const defaultImportRe = /import\s+([A-Za-z_$][\w$]*)\s+from/g;
1343
+ const importedNames = new Set();
1344
+ for (const s of scans) {
1345
+ let m;
1346
+ namedImportRe.lastIndex = 0;
1347
+ while ((m = namedImportRe.exec(s.text)) !== null) {
1348
+ // [^}]* 覆盖多行 import;`X as Y` 取 X(消费的是导出名)
1349
+ for (const part of m[1].split(',')) {
1350
+ const name = part.trim().split(/\s+as\s+/)[0].trim();
1351
+ if (name !== '') { importedNames.add(name); }
1352
+ }
1353
+ }
1354
+ defaultImportRe.lastIndex = 0;
1355
+ while ((m = defaultImportRe.exec(s.text)) !== null) {
1356
+ importedNames.add(m[1]);
1357
+ }
1358
+ }
1359
+
1360
+ // 2. 逐文件找未被消费的 export struct
1361
+ for (const s of scans) {
1362
+ if (/@Entry\b/.test(s.text)) { continue; }
1363
+ const lines = s.text.split('\n');
1364
+ for (let i = 0; i < lines.length; i++) {
1365
+ const dm = /export\s+struct\s+([A-Za-z_$][\w$]*)/.exec(lines[i]);
1366
+ if (dm === null) { continue; }
1367
+ const name = dm[1];
1368
+ if (importedNames.has(name)) { continue; }
1369
+ // 本文件内自用:声明行之外的 `Name(` 实例化(ArkUI 组件实例化形态 Name() / Name({...}))
1370
+ let selfUsed = false;
1371
+ const useRe = new RegExp('\\b' + name + '\\s*\\(');
1372
+ for (let j = 0; j < lines.length; j++) {
1373
+ if (j !== i && useRe.test(lines[j])) { selfUsed = true; break; }
1374
+ }
1375
+ if (selfUsed) { continue; }
1376
+ report('GATE', 'orphan-component', s.rel, i + 1,
1377
+ `导出的自定义组件 '${name}' 未被任何文件 import,也未在本文件内实例化 —— ` +
1378
+ '它永远无法被渲染。self-contained 调度的对话框/面板组件必须在宿主页' +
1379
+ '(或父级接线阶段)被 import 并挂到触发器上;若确为预留,请移出本批转换产物');
1380
+ }
1381
+ }
1382
+ }
1383
+
1305
1384
  /**
1306
1385
  * GATE R12: 代码字面尺寸与 measure_pack.json 实测值偏差超阈值。
1307
1386
  *
@@ -1561,6 +1640,7 @@ function main() {
1561
1640
  checkAndroidImmersive(scans, androidDir);
1562
1641
  }
1563
1642
  checkEntryRoute(scans, etsDir);
1643
+ checkOrphanComponents(scans);
1564
1644
 
1565
1645
  // GATE R12: 代码字面尺寸与 measure_pack 实测偏差超阈值。
1566
1646
  // --ui-info 显式指定 ui_info_root;不给时按 SKILL.md 的默认位置