android2harmony 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/README.md +1 -407
  2. package/agents/self-tester.md +55 -376
  3. package/dist/index.js +238 -124
  4. package/dist/index.js.map +4 -4
  5. package/package.json +36 -32
  6. package/skills/a2h-resource-convert/SKILL.md +931 -0
  7. package/skills/a2h-resource-convert/references/code-vector-icon-rules.md +335 -0
  8. package/skills/{hmos-resources-convert → a2h-resource-convert}/references/conversion-rules.md +15 -2
  9. package/skills/{hmos-resources-convert → a2h-resource-convert}/references/dependency-analysis-rules.md +24 -2
  10. package/skills/a2h-resource-convert/references/lottie-conversion-rules.md +219 -0
  11. package/skills/{hmos-resources-convert → a2h-resource-convert}/references/resource-mapping-rules.md +27 -2
  12. package/skills/{hmos-resources-convert → a2h-resource-convert}/references/xml-drawable-to-svg-rules.md +118 -1
  13. package/skills/a2h-resource-convert/scripts/a2h_resource_convert.js +2186 -0
  14. package/skills/a2h-resource-convert/scripts/app_identity.js +741 -0
  15. package/skills/a2h-resource-convert/scripts/code_vector_icons.js +607 -0
  16. package/skills/a2h-resource-convert/scripts/package.json +3 -0
  17. package/skills/a2h-resource-convert/scripts/svg_fidelity_check.js +632 -0
  18. package/skills/a2h-ui-transfer/SKILL.md +431 -0
  19. package/skills/a2h-ui-transfer/references/conversion-procedure.md +547 -0
  20. package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/_/350/214/203/345/274/217/351/200/211/346/213/251/350/257/264/346/230/216.md +1 -1
  21. package/skills/a2h-ui-transfer/references/mvvm-v2/_/350/243/205/351/245/260/345/231/250/351/200/237/346/237/245.md +133 -0
  22. package/skills/{hmos-batch-ui-align/scripts/android_parse_fast.ts → a2h-ui-transfer/scripts/android_parse_fast.js} +532 -301
  23. package/skills/a2h-ui-transfer/scripts/arkts_static_check.js +1624 -0
  24. package/skills/a2h-ui-transfer/scripts/measure_pack.js +1005 -0
  25. package/skills/a2h-ui-transfer/scripts/package.json +3 -0
  26. package/skills/hmos-fix-build-errors/SKILL.md +1 -1
  27. package/skills/hmos-incremental-ui-align/README.md +15 -15
  28. package/skills/hmos-incremental-ui-align/SKILL.md +15 -15
  29. package/skills/hmos-incremental-ui-align/references/State_Model_Template.md +2 -2
  30. package/skills/hmos-incremental-ui-align/scripts/app_feature_verify.js +790 -0
  31. package/skills/hmos-incremental-ui-align/scripts/extract_checklist.js +285 -0
  32. package/skills/hmos-incremental-ui-align/scripts/navigation-capure.md +76 -76
  33. package/skills/hmos-incremental-ui-align/scripts/page_capture.js +756 -0
  34. package/skills/hmos-incremental-ui-align/scripts/page_capture_burst.js +155 -0
  35. package/skills/hmos-integration-test/README.md +341 -0
  36. package/skills/hmos-integration-test/SKILL.md +446 -0
  37. package/skills/hmos-integration-test/scripts/report-tool.mjs +646 -0
  38. package/skills/hmos-integration-test/scripts/resolve-metadata-tool.mjs +147 -0
  39. package/skills/hmos-integration-test/scripts/self-test-runner.mjs +1006 -0
  40. package/skills/hmos-integration-test/scripts/testcases-tool.mjs +189 -0
  41. package/skills/hmos-spec-generate/SKILL.md +26 -24
  42. package/skills/hmos-spec-generate/scripts/parse_requirements.ts +515 -0
  43. package/skills/hmos-spec-generate/template/REQ.txt +22 -0
  44. package/skills/hmos-spec-generate/template/REQ.xlsx +0 -0
  45. package/skills/hmos-batch-ui-align/SKILL.md +0 -141
  46. package/skills/hmos-batch-ui-align/references/conversion-procedure.md +0 -217
  47. package/skills/hmos-incremental-ui-align/scripts/app_feature_verify.ts +0 -999
  48. package/skills/hmos-incremental-ui-align/scripts/extract_checklist.ts +0 -343
  49. package/skills/hmos-incremental-ui-align/scripts/page_capture.ts +0 -977
  50. package/skills/hmos-incremental-ui-align/scripts/page_capture_burst.ts +0 -188
  51. package/skills/hmos-resources-convert/SKILL.md +0 -654
  52. package/skills/hmos-resources-convert/template/AppScope/app.json5 +0 -10
  53. package/skills/hmos-resources-convert/template/AppScope/resources/base/element/string.json +0 -8
  54. package/skills/hmos-resources-convert/template/AppScope/resources/base/media/background.png +0 -0
  55. package/skills/hmos-resources-convert/template/AppScope/resources/base/media/foreground.png +0 -0
  56. package/skills/hmos-resources-convert/template/AppScope/resources/base/media/layered_image.json +0 -7
  57. package/skills/hmos-resources-convert/template/build-profile.json5 +0 -42
  58. package/skills/hmos-resources-convert/template/code-linter.json5 +0 -32
  59. package/skills/hmos-resources-convert/template/entry/build-profile.json5 +0 -33
  60. package/skills/hmos-resources-convert/template/entry/hvigorfile.ts +0 -6
  61. package/skills/hmos-resources-convert/template/entry/obfuscation-rules.txt +0 -23
  62. package/skills/hmos-resources-convert/template/entry/oh-package.json5 +0 -10
  63. package/skills/hmos-resources-convert/template/entry/src/main/ets/entryability/EntryAbility.ets +0 -48
  64. package/skills/hmos-resources-convert/template/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets +0 -16
  65. package/skills/hmos-resources-convert/template/entry/src/main/ets/pages/Index.ets +0 -23
  66. package/skills/hmos-resources-convert/template/entry/src/main/module.json5 +0 -55
  67. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/color.json +0 -8
  68. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/float.json +0 -8
  69. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/string.json +0 -16
  70. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/background.png +0 -0
  71. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/foreground.png +0 -0
  72. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/layered_image.json +0 -7
  73. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/startIcon.png +0 -0
  74. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/profile/backup_config.json +0 -3
  75. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/profile/main_pages.json +0 -5
  76. package/skills/hmos-resources-convert/template/entry/src/main/resources/dark/element/color.json +0 -8
  77. package/skills/hmos-resources-convert/template/entry/src/mock/mock-config.json5 +0 -2
  78. package/skills/hmos-resources-convert/template/entry/src/ohosTest/ets/test/Ability.test.ets +0 -35
  79. package/skills/hmos-resources-convert/template/entry/src/ohosTest/ets/test/List.test.ets +0 -5
  80. package/skills/hmos-resources-convert/template/entry/src/ohosTest/module.json5 +0 -16
  81. package/skills/hmos-resources-convert/template/entry/src/test/List.test.ets +0 -5
  82. package/skills/hmos-resources-convert/template/entry/src/test/LocalUnit.test.ets +0 -33
  83. package/skills/hmos-resources-convert/template/hvigor/hvigor-config.json5 +0 -23
  84. package/skills/hmos-resources-convert/template/hvigorfile.ts +0 -6
  85. package/skills/hmos-resources-convert/template/oh-package-lock.json5 +0 -28
  86. package/skills/hmos-resources-convert/template/oh-package.json5 +0 -10
  87. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mappings/android-to-harmonyOS-ui-atomic-component-mapping-reference.md +0 -0
  88. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mappings/android-to-harmonyOS-ui-interaction-mapping-reference.md +0 -0
  89. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mappings/android-to-harmonyOS-ui-layout-mapping-reference.md +0 -0
  90. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Link/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/217/214/345/220/221/345/220/214/346/255/245.md" +0 -0
  91. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Observed/350/243/205/351/245/260/345/231/250/345/222/214@ObjectLink/350/243/205/351/245/260/345/231/250/357/274/232/345/265/214/345/245/227/347/261/273/345/257/271/350/261/241/345/261/236/346/200/247/345/217/230/345/214/226.md" +0 -0
  92. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Prop/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/215/225/345/220/221/345/220/214/346/255/245.md" +0 -0
  93. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Provide/350/243/205/351/245/260/345/231/250/345/222/214@Consume/350/243/205/351/245/260/345/231/250/357/274/232/344/270/216/345/220/216/344/273/243/347/273/204/344/273/266/345/217/214/345/220/221/345/220/214/346/255/245.md" +0 -0
  94. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@State/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/347/212/266/346/200/201.md" +0 -0
  95. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Track/350/243/205/351/245/260/345/231/250/357/274/232class/345/257/271/350/261/241/345/261/236/346/200/247/347/272/247/346/233/264/346/226/260.md" +0 -0
  96. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Watch/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/346/233/264/346/224/271/351/200/232/347/237/245.md" +0 -0
  97. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/AppStorage/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200/347/232/204UI/347/212/266/346/200/201/345/255/230/345/202/250.md" +0 -0
  98. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/Environment/357/274/232/350/256/276/345/244/207/347/216/257/345/242/203/346/237/245/350/257/242.md" +0 -0
  99. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/LocalStorage/357/274/232/351/241/265/351/235/242/347/272/247UI/347/212/266/346/200/201/345/255/230/345/202/250.md" +0 -0
  100. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/MVVM/346/250/241/345/274/217/357/274/210V1/357/274/211.md" +0 -0
  101. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/PersistentStorage/357/274/232/346/214/201/344/271/205/345/214/226/345/255/230/345/202/250UI/347/212/266/346/200/201.md" +0 -0
  102. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm//347/256/241/347/220/206/345/272/224/347/224/250/346/213/245/346/234/211/347/232/204/347/212/266/346/200/201/346/246/202/350/277/260.md" +0 -0
  103. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/!!/350/257/255/346/263/225/357/274/232/345/217/214/345/220/221/347/273/221/345/256/232.md" +0 -0
  104. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Computed/350/243/205/351/245/260/345/231/250/357/274/232/350/256/241/347/256/227/345/261/236/346/200/247.md" +0 -0
  105. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Event/350/243/205/351/245/260/345/231/250/357/274/232/350/247/204/350/214/203/347/273/204/344/273/266/350/276/223/345/207/272.md" +0 -0
  106. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Local/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/351/203/250/347/212/266/346/200/201.md" +0 -0
  107. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Monitor/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/344/277/256/346/224/271/345/274/202/346/255/245/347/233/221/345/220/254.md" +0 -0
  108. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@ObservedV2/350/243/205/351/245/260/345/231/250/345/222/214@Trace/350/243/205/351/245/260/345/231/250/357/274/232/347/261/273/345/261/236/346/200/247/345/217/230/345/214/226/350/247/202/346/265/213.md" +0 -0
  109. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Once/350/243/205/351/245/260/345/231/250/357/274/232/345/210/235/345/247/213/345/214/226/345/220/214/346/255/245/344/270/200/346/254/241.md" +0 -0
  110. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Param/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/244/226/351/203/250/350/276/223/345/205/245.md" +0 -0
  111. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Provider/350/243/205/351/245/260/345/231/250/345/222/214@Consumer/350/243/205/351/245/260/345/231/250/357/274/232/350/267/250/347/273/204/344/273/266/345/261/202/347/272/247/345/217/214/345/220/221/345/220/214/346/255/245.md" +0 -0
  112. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Type/350/243/205/351/245/260/345/231/250/357/274/232/346/240/207/350/256/260/347/261/273/345/261/236/346/200/247/347/232/204/347/261/273/345/236/213.md" +0 -0
  113. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/AppStorageV2/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200UI/347/212/266/346/200/201/345/255/230/345/202/250.md" +0 -0
  114. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/MVVM/346/250/241/345/274/217/357/274/210V2/357/274/211.md" +0 -0
  115. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/PersistenceV2/357/274/232/346/214/201/344/271/205/345/214/226/345/255/230/345/202/250UI/347/212/266/346/200/201.md" +0 -0
  116. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2//347/212/266/346/200/201/347/256/241/347/220/206V1/345/220/221V2/350/277/201/347/247/273/344/270/216/346/267/267/347/224/250/346/214/207/345/257/274.md" +0 -0
@@ -0,0 +1,446 @@
1
+ ---
2
+ name: hmos-integration-test
3
+ description: "Run on-device self-test for a HarmonyOS app. Parse test_case.md, install the HAP, execute AutoTest, and produce a verification report. Optionally enters a test-and-fix loop. Triggers on phrases like '跑自测', '运行自测', '执行自测', '自动测试', '设备测试', '跑用例', '跑自动化测试', 'self test', 'run autotest'."
4
+ allowed-tools:
5
+ - Read
6
+ - Bash
7
+ - Agent
8
+ - Glob
9
+ - AskUserQuestion
10
+ - a2h_self_test
11
+ - a2h_self_test_status
12
+ ---
13
+
14
+ # Self-Test Skill
15
+
16
+ You are a **Self-Test Runner**. Your job is to orchestrate test execution directly — parse test_case.md, install the HAP, run batch tests, generate reports — and optionally drive a test-and-fix loop (test → fix → build → retest, default up to 3 rounds, configurable via `max-rounds`). The on-device batch run is driven by two tools: `a2h_self_test` spawns `self-test-runner.mjs` (HAP install + spawn `batch_runner` detached) and returns `RUNNING` immediately — it does **not** block for the test duration; `a2h_self_test_status` polls the run to a terminal state. The model `apiKey` is resolved by the DevEco Code host and piped to the script over stdin — never env/argv, so `adb`/`hdc` descendants cannot see it. The parse / metadata / report helper scripts are still invoked directly via `node`.
17
+
18
+ ---
19
+
20
+ ## Prerequisites
21
+
22
+ The skill requires two user inputs: `test-case-path` and `hap-path`. `project-dir` (the HarmonyOS project root containing `AppScope/app.json5` — the skill resolves `bundle_name`/`app_name` from it via the resolve-metadata tool during round 1 setup) is derived automatically when omitted: walk up from each `hap-path` entry (directory entries first, then file entries' parent dirs), then from `test-case-path`'s directory, looking for a directory containing `AppScope/app.json5`. Ask the user only if derivation finds nothing. `output-path` is optional — if omitted, it defaults to the directory containing `test-case-path`. After the initial test the skill can optionally enter a test-and-fix loop. Each round's outputs are snapshotted into `<output-path>/round-{n}/` (the skill always writes to `<output-path>/` root; round dirs are populated by the snapshot step); the canonical "latest" copies live at `<output-path>/` root.
23
+
24
+ A package is required (this test installs the app on a connected real HarmonyOS device or emulator). `hap-path` accepts **one or more** comma-separated paths; **each entry may be a single `.hap`/`.hsp` file or a directory**. The union of all entries must form the full package set — exactly one entry HAP plus any feature HAPs and **in-app HSPs** (Harmony Shared Packages built together with the app). In-app HSPs cannot install standalone — they depend on the main HAP — so every listed path is gathered and installed together in one `hdc install -r` transaction; the entry HAP and the HSPs **need not live in the same directory**. Confirm the package source(s) with the user before proceeding.
25
+
26
+ ---
27
+
28
+ ## Step 0 — Model Config Check (run first)
29
+
30
+ The multimodal model `apiKey` is **not** read from `HOMETRANS_MODEL_*` env vars or a `~/.hometrans/autotest.yaml` file on the tool path. It is resolved by the DevEco Code host (`A2HHostDeps.resolveModelParams`) and piped to `self-test-runner.mjs` over stdin (`--model-stdin`) by the `a2h_self_test` tool. The key traverses only the OS pipe + process memory — never env/argv/disk-visible channels — so `adb`/`hdc` descendant processes and crash dumps cannot see it.
31
+
32
+ Host-side resolution priority: config `provider.key` > `UI_VERIFY_*` env vars > DevEco OAuth login.
33
+
34
+ If `a2h_self_test` returns an error like `host did not provide model params (apiKey missing)`, **stop and ask the user** to configure one of:
35
+ - set the model `provider.key` (api key / model name / base URL) in DevEco Code config, or
36
+ - export the `UI_VERIFY_*` env vars (e.g. `UI_VERIFY_API_KEY` / `UI_VERIFY_MODEL_NAME` / `UI_VERIFY_BASE_URL`), or
37
+ - log in via DevEco OAuth.
38
+
39
+ Then re-run `a2h_self_test`. Do **not** generate or edit `~/.hometrans/autotest.yaml` — the tool path writes a temporary one (0o600) inside `task_subdir` and deletes it on terminal state.
40
+
41
+ ### Step 0b — @autotest/agent 检查(自动安装)
42
+
43
+ `self-test-runner.mjs` 在启动 batch_runner 前会先定位 `@autotest/agent` 的 `batch_runner.js`(从脚本自身位置向上查找 node_modules,再回退到全局 npm root)。**若找不到,自动安装**:
44
+
45
+ - 先从 `@buaa_smat/hometrans` 的 `package.json` 读取它依赖的 `@autotest/agent` 版本(避免装错成 `latest` 而漏掉所需修复)。
46
+ - 执行 `npm install -g @autotest/agent@<版本>`,然后重试定位。
47
+ - 仍找不到则报错并提示手动安装:`npm install -g <autotest-agent-tarball.tgz>` 或重装 `npm install -g @buaa_smat/hometrans`。
48
+
49
+ > 因此本 skill **不需要**在 Step 0 手动校验 `@autotest/agent`——runner 自愈。仅当 runner 报"auto-install failed"时才需人工介入(典型原因:该版本未发布到 npm,需装 tarball)。
50
+
51
+ ## Step 1 — Parse Inputs
52
+
53
+ Extract the following from the user's message. If any required input is missing, use `AskUserQuestion` with the template below — do not guess and do not invent paths.
54
+
55
+ | Variable | Required | Meaning | Typical Phrasing |
56
+ |----------|----------|---------|------------------|
57
+ | `test-case-path` | yes | Path to test_case.md | "测试用例在...", "用例文件...", "test case path" |
58
+ | `hap-path` | yes | **One or more** comma-separated `.hap`/`.hsp` files or dirs forming the full package set (see **Prerequisites**): one entry HAP + any feature HAPs / in-app HSPs, possibly in different dirs, installed in one transaction. | "HAP路径...", "安装包在...", "包目录...", "hap和hsp在...", "hap/hsp...", "hap file..." |
59
+ | `project-dir` | no (derived from `hap-path` / `test-case-path`; ask only if derivation fails) | Path to the HarmonyOS project root (the directory containing `AppScope/app.json5`). The skill resolves `bundle_name`/`app_name` from it via the resolve-metadata tool during round 1 setup. | "工程目录...", "HarmonyOS工程...", "项目根...", "project dir..." |
60
+ | `output-path` | no (default: directory of `test-case-path`) | Directory for all output artifacts | "输出到...", "产物目录...", "output to..." |
61
+ | `pre-test-case-path` | no | Path to pre_test_case.md | "前置用例...", "pre test case..." |
62
+ | `android-project-path` | no (for fix loop) | Path to Android source project for reference-based fixing | "Android路径...", "android project..." |
63
+ | `max-rounds` | no (default `3`) | Max iterations for the test-and-fix loop. Must be a positive integer (`>= 1`). Only meaningful when the fix loop is enabled. | "最多 X 轮", "max rounds X", "迭代 X 次" |
64
+
65
+ **AskUserQuestion template (use this exact shape — do NOT ad-lib free-form questions):**
66
+
67
+ ```
68
+ question: "缺少必填参数 <param>。请提供具体路径。"
69
+ choices:
70
+ - { value: "<typed_path>", label: "提供具体路径(在补充消息里给出绝对路径)" }
71
+ ```
72
+
73
+ Map the user's reply back to the variable.
74
+
75
+ **Default `output-path`**: If the user did not provide `output-path`, set it to the parent directory of `test-case-path` (the directory portion, without the filename). Use this resolved value everywhere downstream.
76
+
77
+ **Validation command** (platform-agnostic — adapt to OS):
78
+
79
+ Verify that `test-case-path` points to a readable file; that **every** comma-separated entry in `hap-path` points to a readable file **or directory**; and that, across all entries, the aggregated package set contains **at least one `.hap`** (the entry package). `output-path` is an existing directory (create if missing).
80
+
81
+ | OS | Example check |
82
+ |----|---------------|
83
+ | macOS / Linux / bash on Windows | `test -f "<path>" && echo OK` |
84
+ | Native PowerShell on Windows | `Test-Path -LiteralPath "<path>" -PathType Leaf` |
85
+
86
+ Pick whichever shell is available — the operation, not the syntax, is what matters.
87
+
88
+ **HSP auto-discovery guard**: After validating `hap-path`, scan the **parent directory of the entry HAP** and any listed **directory entries** for `*.hsp` files and an `hsp/` subdirectory — this catches the common failure where only the entry HAP is passed but the app needs its in-app HSPs at runtime. For any `*.hsp` **not already covered** by `hap-path`, append its containing directory (or the individual files) to `hap-path` as extra comma-separated entries, and warn the user: "Auto-discovered N in-app HSP(s) in `<path>` and appended to hap-path — omitting them makes the app malfunction on the device." If none are found → proceed normally (single-module project).
89
+
90
+ ---
91
+
92
+ ## Step 2 — Test-and-Fix Loop
93
+
94
+ ### Entry — Ask User (Opt-In for Fix Loop)
95
+
96
+ Before entering the loop, if the user did NOT already say "自动修复" / "fix and retest" / "自动修", ask:
97
+
98
+ > 检测到将进入测试修复循环。是否启用自动修复和重测?(最多 `<MAX_ROUNDS>` 轮,每轮测试失败后自动修代码、重建 HAP、重新测试)
99
+
100
+ Substitute `<MAX_ROUNDS>` in the question text with the parsed value (default `3`).
101
+
102
+ - User says yes / "是" / "好" / "修" → proceed with the full loop below (2.0 → 2A → 2B → 2C → 2D).
103
+ - User says no / "不用" / "skip" → run **2A only** (single test round), skip 2B–2D, snapshot the round directory (2.5), then go to Step 3.
104
+
105
+ > If the user explicitly said "自动修复" / "fix and retest" in the original message, skip the question and enter the full loop directly.
106
+
107
+ ---
108
+
109
+ ### 2.0 — Loop Setup
110
+
111
+ 1. Record `CURRENT_HAP = <hap-path>` (the full comma-separated list as the user gave it) and keep an immutable copy `ORIGINAL_HAP_PATH = <hap-path>`. `CURRENT_HAP` is passed verbatim as `hap-path` to the skill (package-set format: see **Prerequisites**).
112
+ 2. Initialize `round = 1`, `MAX_ROUNDS = <max-rounds from Step 1, default 3>`. If the parsed value is missing or not a positive integer, use `3`.
113
+ 3. Set `ROUND_DIR = <output-path>/round-1`. Create the directory.
114
+ 4. **Resolve `project-dir`** (if the user did not provide it): walk up from each `hap-path` entry — for directory entries, check the entry itself then each parent; for file entries, start from the file's parent — looking for a directory containing `AppScope/app.json5`. If no `hap-path` entry yields it, repeat the walk from `test-case-path`'s directory. If still nothing, use `AskUserQuestion` to get it. Store as `<project-dir>`.
115
+ 5. `PROJECT_ROOT` is **unset** at this point — it will be read after round 1's agent invocation produces `<output-path>/app-metadata.json`.
116
+ 6. Mark step description with round info (e.g., "**Round 1/<MAX_ROUNDS>**").
117
+
118
+ ---
119
+
120
+ ### 2A — Test (per round)
121
+
122
+ The helper scripts (`resolve-metadata-tool.mjs`, `testcases-tool.mjs`, `report-tool.mjs`) are bundled in this skill's own `scripts/` directory (NOT the legacy `$HOMETRANS_TOOL_PATH` copy). Before running any `node` command below, resolve `SKILL_SCRIPTS` to the absolute path of this skill's `scripts/` directory — for opencode that is `~/.config/opencode/skills/hmos-integration-test/scripts`. `self-test-runner.mjs` itself is spawned by the `a2h_self_test` / `a2h_self_test_status` tools (the plugin resolves its path internally), so you do **not** invoke it via `node` yourself. The scripts locate `@autotest/agent`'s `batch_runner.js` by walking up from their own location and falling back to the global npm root, so they run correctly whether invoked from the repo, the skill dir, or a project.
123
+
124
+ `self-test-runner.mjs` orchestrates HAP install/uninstall + batch execution; polling is driven by you via `a2h_self_test_status`. The agent mode (`single` vs `layered` Planner+Executor dual-agent) is set by the `mode` field of the `a2h_self_test` tool call — default `single`; pass `mode: "layered"` when the app is complex enough to benefit from a planning step. There is no `autotest.yaml` to edit on this path — the tool writes a temporary one (0o600) inside `task_subdir` from the stdin-injected key and deletes it on terminal state.
125
+
126
+ **Round 1 — Setup phase (parse + metadata)**:
127
+
128
+ 1. **Resolve app metadata**:
129
+ ```bash
130
+ node "$SKILL_SCRIPTS/resolve-metadata-tool.mjs" --project-dir "<project-dir>" --output "<output-path>/app-metadata.json"
131
+ ```
132
+ Read the JSON from stdout → store `bundle_name`, `app_name`, and `project_root` (→ `PROJECT_ROOT`).
133
+
134
+ 2. **Parse test_case.md** → `<output-path>/_extracted.json` → **Generate testcases.json**:
135
+ ```bash
136
+ node "$SKILL_SCRIPTS/testcases-tool.mjs" "<output-path>/_extracted.json" "<output-path>/testcases.json" --validate
137
+ ```
138
+
139
+ **Pre-cases** (run first in the batch): if `pre-test-case-path` was provided (or `pre_test_case.md` was auto-discovered in the same directory as `test-case-path`), parse its `### Scenario` blocks too — same `{case_name, actions, expected_results}` shape — and **prepend each pre-case to the `cases` array of `_extracted.json` with its `case_name` prefixed by `[PRE] `** (so `testcases-tool.mjs` composes them into `testcases.json` and `report-tool.mjs` flags them as pre-cases via the `[PRE] ` prefix). Pre-cases thus run first in the same batch; their failures are treated as environment/setup issues, not application defects.
140
+
141
+ **Round 2+ — skip setup phase**, reuse `<output-path>/testcases.json` and `<output-path>/app-metadata.json`.
142
+
143
+ **All rounds — Run self-test (install + execute) + poll**:
144
+
145
+ 1. **Start the run** with the `a2h_self_test` tool (returns immediately — it does **not** block for the test duration). Pass:
146
+ - `testcases`: `<output-path>/testcases.json`
147
+ - `hap`: `<CURRENT_HAP>`
148
+ - `bundleName`: `<bundle_name>`
149
+ - `taskDir`: `<output-path>/task`
150
+ - `outputDir`: `<output-path>`
151
+ - `category`: `<app_name>`
152
+ - `mode`: `"single"` (default) or `"layered"` (Planner+Executor dual-agent; use when the app benefits from a planning step). Omit for `single`.
153
+
154
+ The tool spawns `self-test-runner.mjs` (which does `hdc uninstall` + `hdc install -r`, then spawns `batch_runner.js` detached) and resolves the model `apiKey` from the host, piping it over stdin. Parse the returned `output` (stdout JSON) and branch:
155
+ - `{ "status": "RUNNING", "pid": <pid>, "task_dir": "<...>", "task_subdir": "<...>" }` -- store `pid` and `task_subdir` for polling and timeout-kill, then proceed to step 2.
156
+ - `{ "status": "FAILED", "error": "<...>" }` -- an environmental failure before/during spawn (e.g. `@autotest/agent` auto-install failed, or the HAP install / testcases prep raised). The start tool does **not** throw on this; surface the `error` to the user and **stop** (do not poll `a2h_self_test_status` -- there is no run to poll). Typical causes: the target `@autotest/agent` version isn't published to npm (install a tarball), or a device/HAP path problem.
157
+ If the tool errors with `host did not provide model params (apiKey missing)` → stop and see **Step 0**.
158
+
159
+ 2. **Poll until terminal** with the `a2h_self_test_status` tool:
160
+ - `taskDir`: the `task_dir` returned above
161
+ - `outputDir`: `<output-path>`
162
+
163
+ `a2h_self_test_status` reads `summary.json` / `task_results.jsonl` / log mtime and returns a status JSON. Branch on `status`:
164
+
165
+ | `status` | exitCode | Action |
166
+ |---|---|---|
167
+ | `RUNNING` | 2 | If elapsed < `max_elapsed` (= `caseCount × 720s × 1.5`, where `caseCount` is the number of cases in `testcases.json`) → wait ~60s via Bash (`Start-Sleep 60` in PowerShell / `sleep 60` in bash), then retry `a2h_self_test_status`. If elapsed ≥ `max_elapsed` → kill the detached run via Bash (`taskkill /PID <pid> /F` on Windows / `kill <pid>` on Unix), then call `a2h_self_test_status` **once more** so it returns `CRASHED` (the runner detects the dead pid via `batch.pid`) and deletes the temp `autotest.yaml` (apiKey) from `task_subdir`. Record `TIMEOUT`, stop. |
168
+ | `COMPLETED` | 0 | Take `pass_count` / `fail_count` / `unknown_count` / `pass_rate`; proceed to step 3 (generate report). |
169
+ | `CRASHED` | 3 | Write error to user; stop. |
170
+ | `NOT_STARTED` | 4 | Write error to user; stop. |
171
+
172
+ On any terminal state (`COMPLETED`/`CRASHED`/`NOT_STARTED`, or `CRASHED` after the timeout-kill poll), `self-test-runner.mjs` deletes the temporary `autotest.yaml` (the only place the key sat on disk) from `task_subdir`. This is why the timeout-kill flow above ends with one more `a2h_self_test_status` call -- that call is what triggers the deletion.
173
+
174
+ > The detached `batch_runner` has no internal timeout on this path (the run is started without `--timeout`), so the agent enforces `max_elapsed` and kills `pid` when exceeded.
175
+
176
+ 5. **Generate report** — after `COMPLETED`:
177
+ ```bash
178
+ node "$SKILL_SCRIPTS/report-tool.mjs" \
179
+ --task-subdir "<task_subdir from a2h_self_test / a2h_self_test_status>" \
180
+ --app-metadata "<output-path>/app-metadata.json" \
181
+ --hap "<entry-hap-basename>" \
182
+ --device "<device-serial>" \
183
+ --suite "<suite-name>" \
184
+ --out "<output-path>/self-test-report.md" \
185
+ --validate
186
+ ```
187
+
188
+ ---
189
+
190
+ ### 2A.1 — Detect early-exit (sentinel) reports first
191
+
192
+ The agent writes a degraded sentinel report when T1 / T3 / T4 / T6 fail before the case table is rendered. First, guard the empty-suite case — the batch launcher returns early without writing `summary.json` when there are 0 cases, which the sentinel check below would otherwise misreport as a batch crash:
193
+
194
+ - If `<output-path>/testcases.json` exists and contains 0 cases → set `stop_reason = no_testcases`, surface a clear message to the user (`<output-path>/testcases.json contains 0 cases — nothing to run`), snapshot the round (2.5), and exit the loop. Do NOT enter 2B. (The 2A.2 `total == 0` guard remains as a secondary safety net.)
195
+
196
+ Then check that the report exists:
197
+
198
+ - If `<output-path>/self-test-report.md` does **not** exist (e.g., T8 failure or a hard error not covered by the sentinel set) → set `stop_reason = agent_early_exit`, `early_exit_reason = "no self-test-report.md written — agent failed before producing a report"`, snapshot the round (2.5), and exit the loop. Do NOT enter 2B.
199
+
200
+ Then grep the report for the first `status:` line and the first `reason:` line:
201
+
202
+ | Shell | Example |
203
+ |-------|---------|
204
+ | bash / sh | `grep -m1 -E '^status:' "<output-path>/self-test-report.md"` |
205
+ | PowerShell | `Select-String -Path "<output-path>/self-test-report.md" -Pattern '^status:' | Select-Object -First 1` |
206
+
207
+ Run both `status:` and `reason:` checks.
208
+
209
+ - If the first line matches `status: FAIL` → this is a sentinel report. Set `stop_reason = agent_early_exit`, capture the `reason:` line for the user (`early_exit_reason = <reason>`), snapshot the round (2.5), and exit the loop. Do NOT enter 2B (the fixer cannot fix an environment / connectivity / config issue).
210
+ - Otherwise → proceed to 2A.2 below.
211
+
212
+ ---
213
+
214
+ ### 2A.2 — Parse the case counts
215
+
216
+ Use the counts from the `COMPLETED` status JSON returned by `a2h_self_test_status` (step 2 above — no re-read needed):
217
+ - `passed` = `pass_count`
218
+ - `failed` = `fail_count` + `unknown_count` (the report's "失败" = FAIL + UNKNOWN)
219
+ - `total` = `pass_count` + `fail_count` + `unknown_count`
220
+
221
+ **Empty-suite guard**: If `total == 0` (e.g., `testcases.json` happened to be empty / 0 cases), set `stop_reason = no_testcases`, surface a clear message to the user (`<output-path>/testcases.json contains 0 cases — nothing to run`), snapshot the round (2.5), and exit the loop. Do NOT enter the fix loop.
222
+
223
+ Otherwise: Set `round_all_passed = true` if `failed == 0` and `passed == total` (and `total > 0`, which the guard above ensures).
224
+
225
+ > The full overview (with the pre-case / regular split, pass rate, device, suite) is read from `<output-path>/self-test-report.md` in **Step 3** — do not re-read it here.
226
+
227
+ ---
228
+
229
+ ### 2B — Fix (if failed > 0)
230
+
231
+ If `round_all_passed`:
232
+ - Skip 2B and 2C.
233
+ - Set `stop_reason = all_passed`.
234
+ - Snapshot the round (2.5) and exit the loop.
235
+
236
+ If `failed > 0`:
237
+
238
+ Launch `self-test-fixer`. Use `PROJECT_ROOT`:
239
+
240
+ ```
241
+ Agent(
242
+ subagent_type="self-test-fixer",
243
+ description="Fix Self-Test Failures (Round {round})",
244
+ prompt="self_test_report_path: <output-path>/self-test-report.md\nharmony_project_dir: <PROJECT_ROOT>\noutput_path: <ROUND_DIR>"
245
+ )
246
+ ```
247
+
248
+ > `android_project_dir` is optional — if the user mentioned an Android source path, add the line `android_project_dir: <android-project-path>` to the prompt. Omit otherwise.
249
+
250
+ After completion, read `<ROUND_DIR>/self-test-fix-report.md` (first 60 lines). Extract from its 概览 section (Chinese field names per self-test-fixer.md, English semantics in parentheses):
251
+ - `白盒确认问题存在` (confirmed — white-box confirmed defect count)
252
+ - `白盒判定为误报` (false positives count)
253
+ - `修复成功` (successfully fixed count)
254
+ - `修复失败(2次尝试后)` (failed to fix count)
255
+
256
+ Set `round_no_confirmed_defects = true` if `confirmed == 0` (all failures are false positives).
257
+
258
+ Surface a one-line summary:
259
+
260
+ > Round N: test=<passed>/<total>, confirmed=X, fixed=Y, false_positives=Z
261
+
262
+ ---
263
+
264
+ ### 2C — Build (if defects were confirmed)
265
+
266
+ If `round_no_confirmed_defects`:
267
+ - Skip 2C.
268
+ - Set `stop_reason = no_confirmed_defects`.
269
+ - Snapshot the round (2.5) and exit the loop.
270
+
271
+ If `confirmed > 0`:
272
+
273
+ **Capture `BUILD_START`** immediately before invoking `hmos-fix-build-errors` — e.g. `touch "<ROUND_DIR>/.build_start_marker"` (or record a timestamp). This marks the moment the rebuild begins, so the re-collection step below can tell freshly-rebuilt module outputs from stale ones left by a prior build.
274
+
275
+ **Determine signing mode**: Check `ORIGINAL_HAP_PATH` (the user's original input). If it contains `*-signed.hap` or `*-signed.hsp` entries → the original packages were signed (real device path) → pass `--signed` to assert signed output. If not (emulator path, unsigned packages) → omit `--signed` so the skill produces unsigned output. This prevents the rebuilt HAP from failing `hdc install -r` on a real device while allowing unsigned testing on emulators.
276
+
277
+ Then invoke the `hmos-fix-build-errors` skill with `<PROJECT_ROOT>` (and `--signed` if determined above) for a rebuild.
278
+
279
+ After completion, locate the authoritative entry directly from `<PROJECT_ROOT>/entry/build/default/outputs/default/`. Identify `ENTRY_HAP` in this priority order:
280
+
281
+ 1. `<PROJECT_ROOT>/entry/build/default/outputs/default/entry-default-*.hap`
282
+ 2. First `*.hap` under `<PROJECT_ROOT>/entry/build/default/outputs/default/`
283
+
284
+ If no `.hap` is found → report "Build did not produce a HAP — cannot re-test." Set `stop_reason = no_hap`. Snapshot the round (2.5) and exit the loop.
285
+
286
+ **Assemble the next round's install set into `<ROUND_DIR>/package-set/`** — the rebuilt entry + freshly-rebuilt in-app HSPs + any original packages the rebuild did not reproduce, all in ONE directory. Use `<ROUND_DIR>/package-set/` (a dedicated subdir), **not** `<ROUND_DIR>` itself, because step 1 clears the destination and `<ROUND_DIR>` holds the round's reports and `.build_start_marker`.
287
+
288
+ 1. **Clear/create** `<ROUND_DIR>/package-set/`, then copy `ENTRY_HAP` into it. The entry is placed **unconditionally** (it is authoritative; the freshness gate below does NOT apply to it).
289
+ 2. **Re-collect freshly-rebuilt in-app HSPs** from the build tree `<PROJECT_ROOT>/*/build/default/outputs/default/` into `<ROUND_DIR>/package-set/`, admitting **only `*.hsp` whose mtime ≥ `BUILD_START`** (cross-platform: `find "<dir>" -newer "<ROUND_DIR>/.build_start_marker"`, or compare `stat` mtimes). The mtime gate is essential: incremental rebuilds run no clean, so non-entry module dirs still hold *stale* `.hsp` from prior builds — only the ones rebuilt *this* round (mtime ≥ `BUILD_START`) are admitted. **Do NOT re-collect feature HAPs here** (leave them entirely to the carry-forward in step 3). Skip `*ohosTest*` / `*-test-*`.
290
+ 3. **Carry-forward gap-fill** from the **original** package set (`ORIGINAL_HAP_PATH`, expanded: every `.hap`/`.hsp` across all its files/dirs), copying into `<ROUND_DIR>/package-set/` only basenames **still absent** after steps 1–2. **The rebuilt entry is always authoritative — never let an original entry HAP survive next to it:**
291
+ - **In-app HSPs** (`.hsp`): copy forward every one whose basename is not already in `package-set/`. (A freshly-rebuilt HSP from step 2 already occupies its basename → its stale original is correctly skipped.)
292
+ - **The original entry HAP: never carry it forward.** Identify it deterministically: a `.hap` named `entry-*`; else, if there is **exactly one** `.hap` among the original packages, that one. Copying it would leave **two entry HAPs** and install stale code.
293
+ - **Feature HAPs** (any other, clearly non-entry, module `.hap`): copy forward those whose basename is not already in `package-set/`. **If there are multiple non-`entry-*` `.hap` files and none is the sole `.hap`** — you cannot confidently tell feature from entry — **do NOT copy any of them and log that they were dropped.**
294
+
295
+ Use the cross-platform copy syntax from 2.5.
296
+ 4. **Entry-uniqueness fallback:** if `package-set/` does not end with exactly one `entry-*.hap` (or assembly otherwise failed), discard `package-set/` and fall back to **today's** behavior — copy `ENTRY_HAP` from the build tree into `<ROUND_DIR>`, carry the originals forward directly into `<ROUND_DIR>`, and set `CURRENT_HAP = <ROUND_DIR>`. This keeps the loop no-worse-than-before on a malformed build.
297
+ 5. Otherwise set `CURRENT_HAP = <ROUND_DIR>/package-set/`.
298
+
299
+ This yields one directory holding exactly one entry HAP (the rebuilt one) + freshly-rebuilt in-app HSPs + carried-forward originals for anything not rebuilt — collision-free, installed together by the next round's `hdc install -r`. If the original input was a lone `.hap` with no extras and no HSP was rebuilt, `package-set/` simply holds the rebuilt entry.
300
+
301
+ ---
302
+
303
+ ### 2D — Loop Control
304
+
305
+ ```
306
+ # stop_reason may already be set by 2A.1 (agent_early_exit), 2A.2 (no_testcases),
307
+ # 2B (all_passed / no_confirmed_defects), or 2C (no_hap).
308
+ if stop_reason is set: snapshot (2.5) and exit loop
309
+ elif round >= MAX_ROUNDS: stop_reason = max_rounds_reached; snapshot (2.5); exit loop
310
+ else:
311
+ snapshot (2.5) # snapshot the just-completed round
312
+ round += 1
313
+ ROUND_DIR = <output-path>/round-{round}
314
+ create ROUND_DIR
315
+ go to 2A
316
+ ```
317
+
318
+ ---
319
+
320
+ ### 2.5 — Per-round Snapshot
321
+
322
+ After every round's 2A returns (round 1 and rounds 2+), snapshot these files from `<output-path>/` to `<ROUND_DIR>`. Each copy follows the rule **"skip if source missing"**.
323
+
324
+ | Source | Destination | Notes |
325
+ |--------|-------------|-------|
326
+ | `<output-path>/self-test-report.md` | `<ROUND_DIR>/self-test-report.md` | Always expected after a successful T8. If missing, sentinel-FAIL detection in 2A.1 already routed via `agent_early_exit`. |
327
+ | `<output-path>/task/` | `<ROUND_DIR>/task/` | Per-round runner artifacts. Skip if missing (e.g., agent FAILed before T6 created the dir). |
328
+ | `<output-path>/_extracted.json` | `<ROUND_DIR>/_extracted.json` | Only present after round 1 (round 1 setup phase step 2 wrote it). Skip in rounds 2+. |
329
+
330
+ **NOT snapshotted** (these stay at root only as the canonical, cross-round artifacts):
331
+ - `<output-path>/testcases.json` — written once by round 1; rounds 2+ read it in place.
332
+ - `<output-path>/app-metadata.json` — written once by round 1; rounds 2+ read it in place.
333
+
334
+ **`task/` cleanup ordering**: T5 (inside the agent) cleans `<output-path>/task/` at the start of each test invocation. The invariant: by the time T5 wipes the root `task/` at the start of round N, the previous round's `task/` has already been snapshotted into `<output-path>/round-{N-1}/task/`.
335
+
336
+ **Cross-platform copy syntax**:
337
+
338
+ | Shell | Example |
339
+ |-------|---------|
340
+ | bash / sh | `cp -fr "<source>" "<destination>"` |
341
+ | PowerShell | `Copy-Item -LiteralPath "<source>" -Destination "<destination>" -Recurse -Force` |
342
+
343
+ Use forward slashes for cross-platform safety; both shells accept them.
344
+
345
+ ---
346
+
347
+ ### 2.6 — Loop Finalization (package mirror only)
348
+
349
+ When the loop exits, mirror the final package(s) from `CURRENT_HAP` (which may live under the last `<ROUND_DIR>`) to `<output-path>/`. This is the only "latest" artifact whose primary location is a round dir; `self-test-report.md`, `testcases.json`, `app-metadata.json`, and `task/` already live at `<output-path>/` as canonical copies.
350
+
351
+ `CURRENT_HAP` may be a single file, a directory, or a comma-separated list of files/dirs. Walk **every** entry:
352
+ - For each entry that is a **directory** → copy every `.hap`/`.hsp` in it to `<output-path>/` (keep filenames). Do not copy non-package files.
353
+ - For each entry that is a **`.hap`/`.hsp` file** → copy it to `<output-path>/` (a lone single-`.hap` entry is copied to `<output-path>/entry-default.hap`; any other entry keeps its filename).
354
+
355
+ The goal: after finalization, `<output-path>/` holds the **complete installable set** (entry HAP + all HSPs / feature HAPs) so a later `hdc install -r` of every `.hap`/`.hsp` under `<output-path>/` (the engine expands a directory into its `.hap`/`.hsp` file list) reproduces the tested install.
356
+
357
+ Use the same cross-platform copy syntax as above. Skip any copy whose source already resides at the destination.
358
+
359
+ ---
360
+
361
+ ## Step 3 — Surface Results
362
+
363
+ Read `<output-path>/self-test-report.md` (first 80 lines) and extract the overview section:
364
+
365
+ ```
366
+ ## 测试概览
367
+
368
+ - **测试套件**: <suite-name>
369
+ - **测试时间**: <time-range>
370
+ - **设备**: <device-serial>
371
+ - **应用**: <app-name> (<bundle-name>)
372
+ - **HAP**: <entry-hap-basename>
373
+ - **总用例数**: <total>(前置 <pre-total> + 常规 <regular-total>)
374
+ - **通过**: <passed>(前置 <pre-pass> / 常规 <regular-pass>)
375
+ - **失败**: <failed>(FAIL <fail-count> + UNKNOWN <unknown-count>)
376
+ - **常规通过率**: <regular-pass-rate>(仅功能场景,反映本次需求质量)
377
+ - **含前置通过率**: <pass-rate>(仅供整体参考;前置用例属于数据/环境准备,与本次需求功能无关)
378
+ ```
379
+
380
+ > If the report file is missing or malformed, report the error to the user with the agent's output path — do NOT fabricate results. If the report exists but is truncated or unexpectedly formatted, read up to the first 80 lines for the overview section, and point the user to the full file at `<output-path>/self-test-report.md`.
381
+
382
+ **Pre-case reminder**: If pre-cases failed, note that these are environment/setup issues (permissions, media imports, tutorial skipping), NOT application defects. The regular pass rate (常规通过率) is the primary quality metric.
383
+
384
+ **Point the user to the full report**: `<output-path>/self-test-report.md` and per-round artifacts at `<output-path>/round-{N}/`.
385
+
386
+ ### Loop Summary (if loop ran)
387
+
388
+ After the report, present:
389
+
390
+ ```
391
+ ## 自测修复循环完成
392
+
393
+ | 指标 | 值 |
394
+ |------|-----|
395
+ | 迭代次数 | <round>/<MAX_ROUNDS> |
396
+ | 最终状态 | <passed>/<total> 通过(<pass-rate>%) |
397
+ | 停止原因 | <stop_reason> |
398
+ ```
399
+
400
+ `<stop_reason>` is one of:
401
+ - `all_passed` — all cases passed, loop exited successfully.
402
+ - `no_confirmed_defects` — failures exist but fixer judged them all as false positives.
403
+ - `no_hap` — the rebuild did not produce an entry HAP in the build output, so the loop cannot continue.
404
+ - `max_rounds_reached` — hit the configured `MAX_ROUNDS` cap (default 3) with failures remaining.
405
+ - `no_testcases` — `<output-path>/testcases.json` contained 0 cases; nothing to run. Surface the path so the user can inspect.
406
+ - `agent_early_exit` — the skill wrote a sentinel FAIL report (config / device / autotest-dir / batch crash / precondition failure), or no `self-test-report.md` was written (skill failed before producing a report). Surface the captured `early_exit_reason` so the user knows what to fix manually.
407
+
408
+ If failures remain (for `max_rounds_reached`), list them briefly (scenario names from the report overview). Also surface `<ROUND_DIR>/self-test-fix-report.md` when it exists, and describe the final rebuild outcome from the loop summary instead of relying on a removed build-stage report artifact.
409
+
410
+ ---
411
+
412
+ ## Error Handling
413
+
414
+ | Scenario | Action |
415
+ |----------|--------|
416
+ | `test-case-path` does not exist | Ask user for correct path, do not proceed |
417
+ | any `hap-path` entry does not exist | Name the missing entry, ask user for the correct path, do not proceed |
418
+ | `hap-path` entries all exist but none contributes a `.hap` (no entry package) | Report: "No entry HAP found across the given paths — at least one `.hap` is required." Do not proceed |
419
+ | `output-path` parent does not exist | Create it, proceed |
420
+ | Agent returns error / times out | Report the error to user. Suggest: check device connection (`npx --yes devecocli device list`), verify the HAP is valid. If `a2h_self_test` reports `host did not provide model params (apiKey missing)`, configure the model `provider.key` / `UI_VERIFY_*` env / DevEco OAuth (see **Step 0**) |
421
+ | `<output-path>/self-test-report.md` missing after agent completes | Report: "Agent completed but no report was generated. Check agent output at <output-path>." |
422
+ | `self-test-report.md` exists but format is unrecognizable | Show the file path, report the first 80 lines as context, let user investigate |
423
+ | Device not found (agent reports "No device") | Remind user: connect a HarmonyOS device or emulator, verify `npx --yes devecocli device list` shows it |
424
+ | Pre-case failures in report | Note: these are environment issues, not app defects. Highlight regular pass rate |
425
+ | `<output-path>/testcases.json` or `app-metadata.json` missing after round 1 | Round 1 setup may have failed; inspect agent output at `<output-path>/` |
426
+ | `<output-path>/testcases.json` is empty (`total == 0` in 2A) | Report: "testcases.json contains 0 cases — nothing to run." Exit loop with `stop_reason = no_testcases`. Do NOT enter the fix loop. |
427
+ | The skill writes a sentinel FAIL report (first line `status: FAIL`), or no `self-test-report.md` was written (skill failed before producing a report) | Capture the `reason:` line. Exit loop with `stop_reason = agent_early_exit` and surface the captured reason. Do NOT enter the fix loop — these failures (config / device / autotest-dir / crash / timeout / precondition failures / report not produced) are not application defects. |
428
+ | Fixer returns `confirmed == 0` (all false positives) | Exit loop with `stop_reason = no_confirmed_defects`. Surface the fix report summary. |
429
+ | hmos-fix-build-errors produces no HAP in build output | Report: "Build did not produce a HAP — cannot re-test." Exit loop with `stop_reason = no_hap`. |
430
+ | Loop reaches `MAX_ROUNDS` (default 3) with failures remaining | Report `stop_reason = max_rounds_reached`. Surface final pass rate and remaining failures. |
431
+ | `app-metadata.json` missing after round 1 (PROJECT_ROOT unknown) | Surface agent error; PROJECT_ROOT cannot be resolved. Do NOT enter 2B. |
432
+
433
+ ---
434
+
435
+ ## Key Constraints
436
+
437
+ - **Direct orchestration**: The skill runs the parse / metadata / report helper scripts directly via `node "$SKILL_SCRIPTS/..."` (scripts bundled in the skill's own `scripts/` directory), and drives the on-device batch run via the `a2h_self_test` + `a2h_self_test_status` tools (which spawn `self-test-runner.mjs` internally — the plugin resolves its path; the model key is piped over stdin, never env/argv). `batch_runner.js` is resolved from `@autotest/agent` internally (walk-up + global npm root fallback + auto-install). This SKILL.md is the **single source of truth** for the procedure. The `self-tester` **subagent** (`agents/self-tester.md`) is a thin wrapper that loads this skill — callers (e.g. `hmos-convert-pipeline` Stage 4) may launch it for context isolation on long runs; it forwards the same kebab params and reports the result back, without duplicating any procedure. The legacy `$HOMETRANS_TOOL_PATH` scripts (tools/test-tools/autotest) and the `~/.hometrans/autotest.yaml` + `HOMETRANS_MODEL_*` env path are no longer used.
438
+ - **Parameter purity**: Pass only parameter values to the agent's prompt in `key: value` format. Do NOT add extra instructions, format descriptions, schema hints, or implementation suggestions. The agent has its own built-in workflow.
439
+ - **Do NOT read full reports**: Self-test reports can be very large (10KB–200KB). Only read the overview (first 80 lines for test report, first 60 lines for fix report). Point the user to the full file; never fabricate results when a report is missing or unreadable.
440
+ - **Quote all paths**: Paths may contain spaces. Always wrap paths in double quotes in commands.
441
+ - **Pre-cases are environment scripts**: Their failures indicate testing-environment issues, not app bugs. The regular pass rate is the primary quality metric.
442
+ - **Fix-and-retest loop is opt-in**: Follow Step 2 Entry — ask the user unless they already opted in with "自动修复".
443
+ - **Test → Fix → Build is the mandatory order**: Never re-test without rebuilding after a fix (would test old code). Never skip the build step in the loop.
444
+ - **`CURRENT_HAP` must track the latest build**: After each 2C rebuild, update `CURRENT_HAP` to the new HAP from `ROUND_DIR`. Never re-test with the old HAP.
445
+ - **Per-round snapshots**: Each round writes its latest artifacts to `<output-path>/` (root), then the SKILL snapshots per-round outputs to `<output-path>/round-{N}/`. The persistent JSONs (`testcases.json`, `app-metadata.json`) are written exactly once by round 1 and consumed unchanged by rounds 2+. Never pass `output_path: <output-path>/round-N` to the agent — it always writes to `<output-path>/` root; round dirs are populated only by this snapshot step.
446
+ - **Loop iterations are strictly sequential**: Never launch more than one fixer or tester at a time.