android2harmony 0.1.2 → 0.1.3

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 (56) hide show
  1. package/agents/self-tester.md +339 -0
  2. package/dist/index.js +625 -7
  3. package/dist/index.js.map +4 -4
  4. package/package.json +37 -29
  5. package/skills/hmos-incremental-ui-align/README.md +251 -0
  6. package/skills/hmos-incremental-ui-align/SKILL.md +365 -0
  7. package/skills/hmos-incremental-ui-align/diff_analysis.md +53 -0
  8. package/skills/hmos-incremental-ui-align/page_align.md +62 -0
  9. package/skills/hmos-incremental-ui-align/references/Comparison_Template.md +38 -0
  10. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@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 +648 -0
  11. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@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 +2089 -0
  12. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@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 +1033 -0
  13. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@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 +1183 -0
  14. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@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 +576 -0
  15. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@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 +297 -0
  16. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@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 +395 -0
  17. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/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 +903 -0
  18. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/Environment/357/274/232/350/256/276/345/244/207/347/216/257/345/242/203/346/237/245/350/257/242.md +106 -0
  19. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/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 +1178 -0
  20. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/MVVM/346/250/241/345/274/217V1.md +911 -0
  21. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/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 +355 -0
  22. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243//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 +11 -0
  23. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/!!/350/257/255/346/263/225/357/274/232/345/217/214/345/220/221/347/273/221/345/256/232.md +206 -0
  24. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@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 +373 -0
  25. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@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 +158 -0
  26. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@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 +750 -0
  27. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@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 +1704 -0
  28. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@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 +1012 -0
  29. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@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 +164 -0
  30. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@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 +840 -0
  31. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@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 +856 -0
  32. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@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 +83 -0
  33. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/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 +294 -0
  34. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/MVVM/346/250/241/345/274/217/357/274/210V2/357/274/211.md +1407 -0
  35. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/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 +1220 -0
  36. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/_/350/214/203/345/274/217/351/200/211/346/213/251/350/257/264/346/230/216.md +47 -0
  37. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2//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 +840 -0
  38. package/skills/hmos-incremental-ui-align/references/State_Model_Template.md +74 -0
  39. package/skills/hmos-incremental-ui-align/references/UI_Analysis_Template.md +34 -0
  40. package/skills/hmos-incremental-ui-align/references/android-to-harmonyOS-ui-atomic-component-mapping-reference.md +2533 -0
  41. package/skills/hmos-incremental-ui-align/references/android-to-harmonyOS-ui-interaction-mapping-reference.md +555 -0
  42. package/skills/hmos-incremental-ui-align/references/android-to-harmonyOS-ui-layout-mapping-reference.md +117 -0
  43. package/skills/hmos-incremental-ui-align/scripts/app_feature_verify.ts +999 -0
  44. package/skills/hmos-incremental-ui-align/scripts/extract_checklist.ts +343 -0
  45. package/skills/hmos-incremental-ui-align/scripts/navigation-capure.md +76 -0
  46. package/skills/hmos-incremental-ui-align/scripts/page_capture.ts +977 -0
  47. package/skills/hmos-incremental-ui-align/scripts/page_capture_burst.ts +188 -0
  48. package/tools/autotest/deps/autotest-agent-0.1.1.tgz +0 -0
  49. package/tools/autotest/engine/batch-launcher.ts +326 -0
  50. package/tools/autotest/engine/report-tool.ts +773 -0
  51. package/tools/autotest/engine/self-test-runner.ts +1024 -0
  52. package/tools/autotest/engine/testcases-tool.ts +246 -0
  53. package/tools/autotest/resolve-metadata-tool.ts +143 -0
  54. package/tools/autotest/runner/logger.ts +45 -0
  55. package/tools/autotest/runner/process-utils.ts +41 -0
  56. package/tools/autotest/validate.ts +115 -0
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/index.ts", "../src/paths.ts", "../src/discovery.ts", "../src/env.ts"],
4
- "sourcesContent": ["import path from \"node:path\"\r\nimport type { A2HHostDeps, Plugin, PluginInput, PluginHooks } from \"./types\"\r\nimport { PKG_ROOT } from \"./paths\"\r\nimport { discoverA2HAgents } from \"./discovery\"\r\nimport { buildA2HEnvBase } from \"./env\"\r\n\r\n// Public re-exports \u2014 the package's external surface (dev eco code loads\r\n// dist/index.js at runtime and calls `createServer`; `A2HHostDeps` /\r\n// `PLUGIN_ID` are kept exported for any TS consumers).\r\nexport type { A2HHostDeps } from \"./types\"\r\nexport { PLUGIN_ID } from \"./paths\"\r\n\r\n/**\r\n * Create the A2H DevEco Code plugin.\r\n *\r\n * This is a self-contained plugin - skills/, agents/, and tools/\r\n * are bundled inside the package, NOT referenced from HomeTrans.\r\n * A2H-Plugin is the independently publishable \"plugin edition\" of HomeTrans.\r\n *\r\n * @param deps - host dependencies injected by DevEco Code's thin bridge.\r\n * The A2H Plugin defines what it needs (A2HHostDeps);\r\n * DevEco Code provides how to resolve it.\r\n * @returns A Plugin function ready to be registered as a built-in plugin.\r\n */\r\nexport function createServer(deps: A2HHostDeps): Plugin {\r\n return async (input: PluginInput): Promise<PluginHooks> => {\r\n // All A2H assets live inside this package (self-contained, no HomeTrans dep)\r\n const skillsRoot = path.join(PKG_ROOT, \"skills\")\r\n const agentsRoot = path.join(PKG_ROOT, \"agents\")\r\n\r\n // Auto-discover agents: scan top-level agents/*.md, parse `name:` from\r\n // frontmatter, strip frontmatter for prompt body. Adding/removing/renaming\r\n // an agent is just a file drop \u2014 no code change, no rebuild.\r\n const agentPrompts = discoverA2HAgents(agentsRoot)\r\n\r\n return {\r\n dispose: async () => {\r\n agentPrompts.clear()\r\n },\r\n\r\n // \u2500\u2500 config hook: register skills search paths and agents \u2500\u2500\r\n config: async (cfg: Record<string, unknown>) => {\r\n // --- Skills ---\r\n const skills = (cfg.skills as Record<string, unknown>) || {}\r\n cfg.skills = skills\r\n\r\n // Register the bundled skills/ directory as an additional search path.\r\n // All bundled skills are user-visible \u2014 there is no partial-visibility\r\n // (hidden) layer anymore, so we only register the search path here.\r\n const paths = (skills.paths as string[]) || []\r\n skills.paths = paths\r\n if (!paths.includes(skillsRoot)) {\r\n paths.push(skillsRoot)\r\n }\r\n\r\n // --- Agents ---\r\n const agent = (cfg.agent as Record<string, Record<string, unknown>>) || {}\r\n cfg.agent = agent\r\n\r\n for (const [name, prompt] of agentPrompts) {\r\n // Don't overwrite if the user already configured this agent\r\n if (!agent[name]) {\r\n agent[name] = {\r\n mode: \"subagent\",\r\n hidden: true,\r\n prompt,\r\n }\r\n }\r\n }\r\n\r\n },\r\n\r\n // \u2500\u2500 shell.env hook: inject DevEco Studio paths as env vars \u2500\u2500\r\n // NOTE: deliberately uses the *base* env \u2014 the multimodal model apiKey\r\n // is NOT injected into LLM shell sessions (would leak into every command\r\n // and the transcript).\r\n \"shell.env\": async (\r\n shellInput: { cwd: string; sessionID?: string; callID?: string },\r\n output: { env: Record<string, string> },\r\n ) => {\r\n const a2hEnv = await buildA2HEnvBase(deps)\r\n Object.assign(output.env, a2hEnv)\r\n },\r\n }\r\n }\r\n}\r\n", "import path from \"node:path\"\r\nimport { fileURLToPath } from \"node:url\"\r\n\r\n// Resolve package root (one directory above dist/ or src/). Computed once here\r\n// and imported everywhere else so there's a single source of truth. After\r\n// esbuild bundling, import.meta.url points at dist/index.js, so this still\r\n// resolves to the package root at runtime.\r\nconst __filename = fileURLToPath(import.meta.url)\r\nconst __dirname = path.dirname(__filename)\r\n\r\n/** Absolute path to the A2H-Plugin package root. */\r\nexport const PKG_ROOT = path.resolve(__dirname, \"..\")\r\n\r\n/** Public plugin id. */\r\nexport const PLUGIN_ID = \"android2harmony\"\r\n", "import path from \"node:path\"\r\nimport fs from \"node:fs\"\r\n\r\n/**\r\n * Strip YAML frontmatter (--- ... ---) from agent .md files.\r\n * Agent .md files have metadata in frontmatter; only the body\r\n * should be used as the LLM prompt.\r\n */\r\nexport function stripFrontmatter(content: string): string {\r\n const match = content.match(/^---\\s*\\n([\\s\\S]*?)\\n---\\s*\\n/)\r\n return match ? content.slice(match[0].length).trim() : content\r\n}\r\n\r\n/**\r\n * Minimal YAML frontmatter extractor for A2H-owned `.md` files (SKILL.md\r\n * and agent `.md`). A2H controls the frontmatter format, so we only need\r\n * the top-level `name` (a scalar). This avoids pulling a YAML dependency\r\n * into the plugin.\r\n *\r\n * Returns the parsed `name`, or null when the file has no usable frontmatter\r\n * (e.g. a README dropped into a directory that's also scanned for agents).\r\n */\r\nexport function parseFrontmatter(content: string): string | null {\r\n const match = content.match(/^---\\s*\\r?\\n([\\s\\S]*?)\\r?\\n---/)\r\n if (!match) return null\r\n const lines = match[1].split(/\\r?\\n/)\r\n\r\n for (const line of lines) {\r\n if (!line.trim() || line.trim().startsWith(\"#\")) continue\r\n if (line.length - line.trimStart().length !== 0) continue // skip indented (e.g. metadata block) lines\r\n\r\n const kv = line.trim().match(/^([a-zA-Z_][a-zA-Z0-9_-]*):\\s*(.*)$/)\r\n if (!kv) continue\r\n if (kv[1] === \"name\") {\r\n return kv[2].trim().replace(/^[\"']|[\"']$/g, \"\")\r\n }\r\n }\r\n\r\n return null\r\n}\r\n\r\n/**\r\n * Scan the bundled agents/ directory and return a `name \u2192 prompt body` map.\r\n *\r\n * Only top-level `.md` files are considered (subdirectories like `scripts/`\r\n * are skipped via `isFile()`). Files without a `name:` field in frontmatter\r\n * (e.g. a stray README) are silently skipped \u2014 the frontmatter name is the\r\n * source of truth, not the file basename. The body (frontmatter stripped)\r\n * becomes the LLM prompt.\r\n *\r\n * Adding a new agent is therefore a zero-code, zero-rebuild change: just\r\n * drop an `.md` with `name:` frontmatter into `agents/`.\r\n */\r\nexport function discoverA2HAgents(agentsRoot: string): Map<string, string> {\r\n const result = new Map<string, string>()\r\n let entries: fs.Dirent[]\r\n try {\r\n entries = fs.readdirSync(agentsRoot, { withFileTypes: true })\r\n } catch {\r\n return result\r\n }\r\n for (const entry of entries) {\r\n if (!entry.isFile() || !entry.name.endsWith(\".md\")) continue\r\n const file = path.join(agentsRoot, entry.name)\r\n let raw: string\r\n try {\r\n raw = fs.readFileSync(file, \"utf-8\")\r\n } catch {\r\n continue\r\n }\r\n const name = parseFrontmatter(raw)\r\n if (!name) continue // not a valid agent file (no `name:` frontmatter)\r\n result.set(name, stripFrontmatter(raw))\r\n }\r\n return result\r\n}\r\n", "import path from \"node:path\"\r\nimport type { A2HHostDeps } from \"./types\"\r\n\r\n/**\r\n * Build the A2H env var set from host-injected deps. Shared by the `shell.env`\r\n * hook (so every shell session sees these).\r\n * Env vars are the single source of truth in the plugin context.\r\n *\r\n * This base set contains NO secrets \u2014 only DevEco/SDK paths. The multimodal\r\n * model apiKey is intentionally NOT included: the `shell.env` hook uses this\r\n * base set so the key never leaks into every LLM shell command (and thus the\r\n * transcript).\r\n */\r\nexport async function buildA2HEnvBase(\r\n deps: A2HHostDeps,\r\n): Promise<Record<string, string>> {\r\n const env: Record<string, string> = {}\r\n\r\n const home = await deps.resolveDevEcoHome()\r\n if (home) {\r\n const sdk = deps.resolveSdkPath(home)\r\n env[\"DEVECO_HOME\"] = home\r\n env[\"DEVECO_SDK_HOME\"] = sdk\r\n env[\"OHOS_SDK_PATH\"] = path.join(sdk, \"default\", \"openharmony\", \"ets\")\r\n env[\"HMS_SDK_PATH\"] = path.join(sdk, \"default\", \"hms\", \"ets\")\r\n }\r\n\r\n return env\r\n}\r\n"],
5
- "mappings": "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",
6
- "names": ["path", "path", "path", "path"]
3
+ "sources": ["../src/index.ts", "../src/paths.ts", "../src/discovery.ts", "../src/env.ts", "../src/tools/a2h-app-feature-verify.ts", "../src/tools/a2h-autotest-resolve-metadata.ts", "../src/tools/autotest-shared.ts", "../src/tools/a2h-autotest-testcases.ts", "../src/tools/a2h-autotest-report.ts", "../src/tools/a2h-autotest-selftest.ts"],
4
+ "sourcesContent": ["import path from \"node:path\"\r\nimport type { A2HHostDeps, Plugin, PluginInput, PluginHooks } from \"./types\"\r\nimport { PKG_ROOT } from \"./paths\"\r\nimport { discoverA2HAgents } from \"./discovery\"\r\nimport { buildA2HEnvBase } from \"./env\"\r\nimport { makeA2hAppFeatureVerifyTool } from \"./tools/a2h-app-feature-verify\"\r\nimport { makeA2hAutotestResolveMetadataTool } from \"./tools/a2h-autotest-resolve-metadata\"\r\nimport { makeA2hAutotestTestcasesTool } from \"./tools/a2h-autotest-testcases\"\r\nimport { makeA2hAutotestReportTool } from \"./tools/a2h-autotest-report\"\r\nimport { makeA2hAutotestSelftestTool } from \"./tools/a2h-autotest-selftest\"\r\n\r\n// Public re-exports \u2014 the package's external surface (dev eco code loads\r\n// dist/index.js at runtime and calls `createServer`; `A2HHostDeps` /\r\n// `PLUGIN_ID` are kept exported for any TS consumers).\r\nexport type { A2HHostDeps } from \"./types\"\r\nexport { PLUGIN_ID } from \"./paths\"\r\n\r\n/**\r\n * Create the A2H DevEco Code plugin.\r\n *\r\n * This is a self-contained plugin - skills/, agents/, and tools/\r\n * are bundled inside the package, NOT referenced from HomeTrans.\r\n * A2H-Plugin is the independently publishable \"plugin edition\" of HomeTrans.\r\n *\r\n * @param deps - host dependencies injected by DevEco Code's thin bridge.\r\n * The A2H Plugin defines what it needs (A2HHostDeps);\r\n * DevEco Code provides how to resolve it.\r\n * @returns A Plugin function ready to be registered as a built-in plugin.\r\n */\r\nexport function createServer(deps: A2HHostDeps): Plugin {\r\n return async (input: PluginInput): Promise<PluginHooks> => {\r\n // All A2H assets live inside this package (self-contained, no HomeTrans dep)\r\n const skillsRoot = path.join(PKG_ROOT, \"skills\")\r\n const agentsRoot = path.join(PKG_ROOT, \"agents\")\r\n\r\n // Auto-discover agents: scan top-level agents/*.md, parse `name:` from\r\n // frontmatter, strip frontmatter for prompt body. Adding/removing/renaming\r\n // an agent is just a file drop \u2014 no code change, no rebuild.\r\n const agentPrompts = discoverA2HAgents(agentsRoot)\r\n\r\n return {\r\n dispose: async () => {\r\n agentPrompts.clear()\r\n },\r\n\r\n // \u2500\u2500 config hook: register skills search paths and agents \u2500\u2500\r\n config: async (cfg: Record<string, unknown>) => {\r\n // --- Skills ---\r\n const skills = (cfg.skills as Record<string, unknown>) || {}\r\n cfg.skills = skills\r\n\r\n // Register the bundled skills/ directory as an additional search path.\r\n // All bundled skills are user-visible \u2014 there is no partial-visibility\r\n // (hidden) layer anymore, so we only register the search path here.\r\n const paths = (skills.paths as string[]) || []\r\n skills.paths = paths\r\n if (!paths.includes(skillsRoot)) {\r\n paths.push(skillsRoot)\r\n }\r\n\r\n // --- Agents ---\r\n const agent = (cfg.agent as Record<string, Record<string, unknown>>) || {}\r\n cfg.agent = agent\r\n\r\n for (const [name, prompt] of agentPrompts) {\r\n // Don't overwrite if the user already configured this agent\r\n if (!agent[name]) {\r\n agent[name] = {\r\n mode: \"subagent\",\r\n hidden: true,\r\n prompt,\r\n }\r\n }\r\n }\r\n\r\n },\r\n\r\n // \u2500\u2500 shell.env hook: inject DevEco Studio paths + multimodal model \u2500\u2500\r\n // params (from A2HHostDeps.resolveModelParams) as env vars, so the\r\n // UI-alignment scripts (app_feature_verify.ts) can call the model\r\n // without falling back to ~/.hometrans/config.json.\r\n \"shell.env\": async (\r\n shellInput: { cwd: string; sessionID?: string; callID?: string },\r\n output: { env: Record<string, string> },\r\n ) => {\r\n const a2hEnv = await buildA2HEnvBase(deps)\r\n Object.assign(output.env, a2hEnv)\r\n },\r\n\r\n // \u2500\u2500 tool hook: run the UI-verification phone agent without leaking the\r\n // model apiKey into env/argv \u2014 it is resolved from the host and piped to\r\n // the script over stdin. See src/tools/a2h-app-feature-verify.ts.\r\n //\r\n // The autotest_* tools wrap the engine scripts under tools/autotest/\r\n // (vendored from HomeTrans). They are grouped by noun \u2014 each tool\r\n // dispatches on an `action` enum (e.g. `a2h_autotest_selftest` covers\r\n // run|status|kill|check_hap|check_inputs|check_pre), so 11 engine\r\n // subcommands surface as 4 plugin tools. Only `a2h_autotest_selftest`\r\n // with `action:\"run\"` needs a model, resolved via deps.resolveModelParams\r\n // and piped over stdin with the same secret-handling discipline; the\r\n // rest are pure JSON/Markdown transforms or state probes with no model\r\n // and no device.\r\n tool: {\r\n a2h_app_feature_verify: makeA2hAppFeatureVerifyTool(deps),\r\n a2h_autotest_resolve_metadata: makeA2hAutotestResolveMetadataTool(deps),\r\n a2h_autotest_testcases: makeA2hAutotestTestcasesTool(deps),\r\n a2h_autotest_report: makeA2hAutotestReportTool(deps),\r\n a2h_autotest_selftest: makeA2hAutotestSelftestTool(deps),\r\n },\r\n }\r\n }\r\n}\r\n", "import path from \"node:path\"\r\nimport { fileURLToPath } from \"node:url\"\r\n\r\n// Resolve package root (one directory above dist/ or src/). Computed once here\r\n// and imported everywhere else so there's a single source of truth. After\r\n// esbuild bundling, import.meta.url points at dist/index.js, so this still\r\n// resolves to the package root at runtime.\r\nconst __filename = fileURLToPath(import.meta.url)\r\nconst __dirname = path.dirname(__filename)\r\n\r\n/** Absolute path to the A2H-Plugin package root. */\r\nexport const PKG_ROOT = path.resolve(__dirname, \"..\")\r\n\r\n/** Public plugin id. */\r\nexport const PLUGIN_ID = \"android2harmony\"\r\n", "import path from \"node:path\"\r\nimport fs from \"node:fs\"\r\n\r\n/**\r\n * Strip YAML frontmatter (--- ... ---) from agent .md files.\r\n * Agent .md files have metadata in frontmatter; only the body\r\n * should be used as the LLM prompt.\r\n */\r\nexport function stripFrontmatter(content: string): string {\r\n const match = content.match(/^---\\s*\\n([\\s\\S]*?)\\n---\\s*\\n/)\r\n return match ? content.slice(match[0].length).trim() : content\r\n}\r\n\r\n/**\r\n * Minimal YAML frontmatter extractor for A2H-owned `.md` files (SKILL.md\r\n * and agent `.md`). A2H controls the frontmatter format, so we only need\r\n * the top-level `name` (a scalar). This avoids pulling a YAML dependency\r\n * into the plugin.\r\n *\r\n * Returns the parsed `name`, or null when the file has no usable frontmatter\r\n * (e.g. a README dropped into a directory that's also scanned for agents).\r\n */\r\nexport function parseFrontmatter(content: string): string | null {\r\n const match = content.match(/^---\\s*\\r?\\n([\\s\\S]*?)\\r?\\n---/)\r\n if (!match) return null\r\n const lines = match[1].split(/\\r?\\n/)\r\n\r\n for (const line of lines) {\r\n if (!line.trim() || line.trim().startsWith(\"#\")) continue\r\n if (line.length - line.trimStart().length !== 0) continue // skip indented (e.g. metadata block) lines\r\n\r\n const kv = line.trim().match(/^([a-zA-Z_][a-zA-Z0-9_-]*):\\s*(.*)$/)\r\n if (!kv) continue\r\n if (kv[1] === \"name\") {\r\n return kv[2].trim().replace(/^[\"']|[\"']$/g, \"\")\r\n }\r\n }\r\n\r\n return null\r\n}\r\n\r\n/**\r\n * Scan the bundled agents/ directory and return a `name \u2192 prompt body` map.\r\n *\r\n * Only top-level `.md` files are considered (subdirectories like `scripts/`\r\n * are skipped via `isFile()`). Files without a `name:` field in frontmatter\r\n * (e.g. a stray README) are silently skipped \u2014 the frontmatter name is the\r\n * source of truth, not the file basename. The body (frontmatter stripped)\r\n * becomes the LLM prompt.\r\n *\r\n * Adding a new agent is therefore a zero-code, zero-rebuild change: just\r\n * drop an `.md` with `name:` frontmatter into `agents/`.\r\n */\r\nexport function discoverA2HAgents(agentsRoot: string): Map<string, string> {\r\n const result = new Map<string, string>()\r\n let entries: fs.Dirent[]\r\n try {\r\n entries = fs.readdirSync(agentsRoot, { withFileTypes: true })\r\n } catch {\r\n return result\r\n }\r\n for (const entry of entries) {\r\n if (!entry.isFile() || !entry.name.endsWith(\".md\")) continue\r\n const file = path.join(agentsRoot, entry.name)\r\n let raw: string\r\n try {\r\n raw = fs.readFileSync(file, \"utf-8\")\r\n } catch {\r\n continue\r\n }\r\n const name = parseFrontmatter(raw)\r\n if (!name) continue // not a valid agent file (no `name:` frontmatter)\r\n result.set(name, stripFrontmatter(raw))\r\n }\r\n return result\r\n}\r\n", "import path from \"node:path\"\nimport type { A2HHostDeps } from \"./types\"\n\n/**\n * Build the A2H env var set from host-injected deps. Shared by the `shell.env`\n * hook (so every shell session sees these). Env vars are the single source of\n * truth in the plugin context for non-secret paths.\n *\n * Contains only DevEco/SDK path vars. The multimodal model apiKey is NOT\n * injected here anymore \u2014 putting it in `process.env` leaked it to every\n * descendant process (adb/hdc, sub-agents), to `/proc/<pid>/environ`, crash\n * dumps, and any `env`/`printenv` output. Instead the `a2h_app_feature_verify`\n * plugin tool resolves the params via `A2HHostDeps.resolveModelParams` and pipes\n * them to the script over stdin (`--model-stdin`), so the key stays in process\n * memory + the stdin pipe and never enters env/argv/disk.\n *\n * Nothing secret is present in this env set, but it is still good hygiene not\n * to dump it into logs verbatim.\n */\nexport async function buildA2HEnvBase(\n deps: A2HHostDeps,\n): Promise<Record<string, string>> {\n const env: Record<string, string> = {}\n\n const home = await deps.resolveDevEcoHome()\n if (home) {\n const sdk = deps.resolveSdkPath(home)\n env[\"DEVECO_HOME\"] = home\n env[\"DEVECO_SDK_HOME\"] = sdk\n env[\"OHOS_SDK_PATH\"] = path.join(sdk, \"default\", \"openharmony\", \"ets\")\n env[\"HMS_SDK_PATH\"] = path.join(sdk, \"default\", \"hms\", \"ets\")\n }\n\n return env\n}\n", "import { spawn } from \"node:child_process\"\nimport path from \"node:path\"\nimport { PKG_ROOT } from \"../paths\"\nimport type { A2HHostDeps, ToolContext, ToolDefinition } from \"../types\"\n\n// The bundled UI-feature-verification script (TypeScript, run directly by\n// Node \u2265 22.18 which strips types natively \u2014 same as when the agent ran\n// `node app_feature_verify.ts` via the shell).\nconst SCRIPT_PATH = path.join(\n PKG_ROOT,\n \"skills\",\n \"hmos-incremental-ui-align\",\n \"scripts\",\n \"app_feature_verify.ts\",\n)\n\n/**\n * Env vars that may carry a model apiKey. Stripped from the child's env so the\n * key is never inherited by the spawned script or its adb/hdc descendants. The\n * script instead receives the key via the stdin pipe (`--model-stdin`).\n */\nconst SECRET_ENV = [\n \"HOMETRANS_MODEL_API_KEY\",\n \"HOMETRANS_MODEL_NAME\",\n \"HOMETRANS_MODEL_BASE_URL\",\n \"GLM_API_KEY\",\n \"TEST_API_KEY\",\n] as const\n\n/** Cap stdout returned to the host so a runaway loop can't overflow the chat. */\nfunction cap(s: string, max = 50000): string {\n if (s.length <= max) return s\n const half = Math.floor(max / 2)\n return s.slice(0, half) + `\\n\u2026[truncated ${s.length - max} chars]\u2026\\n` + s.slice(-half)\n}\n\n/**\n * Build the `a2h_app_feature_verify` plugin tool.\n *\n * Spawns app_feature_verify.ts with the model config piped to its stdin, so the\n * apiKey travels only through the OS pipe + process memory \u2014 never env, argv,\n * or a plugin-written file. The host resolves the params (apiKey) via\n * `deps.resolveModelParams`; the tool is the only thing that ever holds it.\n */\nexport function makeA2hAppFeatureVerifyTool(deps: A2HHostDeps): ToolDefinition {\n return {\n description:\n \"Run the a2h UI feature-verification phone agent (app_feature_verify.ts) to navigate an app and verify a feature path on a connected device. The multimodal model apiKey is resolved from the host and piped to the script over stdin \u2014 it is never placed in env or argv, so it is not visible to the spawned adb/hdc subprocesses. Use this instead of running `node app_feature_verify.ts` directly. The script runs its internal agent loop (screenshot \u2192 model \u2192 action) up to maxSteps and prints per-step thinking + actions; the full stdout is returned on success. One of task/prompt/feature is required; app and package are required.\",\n args: {\n type: \"object\",\n properties: {\n app: { type: \"string\", description: \"\u76EE\u6807 app \u663E\u793A\u540D\uFF08required\uFF09\" },\n package: { type: \"string\", description: \"\u76EE\u6807 app \u5305\u540D\uFF08required\uFF09\" },\n device: {\n type: \"string\",\n enum: [\"hdc\", \"adb\"],\n default: \"hdc\",\n description: \"\u8BBE\u5907\u9A71\u52A8\uFF1Ahdc(HarmonyOS) / adb(Android)\",\n },\n task: {\n type: \"string\",\n description: \"L1/L2/L3 \u4EFB\u52A1\u8DEF\u5F84\uFF0C\u5982 L1\u76F8\u518C\u8BE6\u60C5L2\u66F4\u591A\uFF08\u4E0E prompt/feature \u4E09\u9009\u4E00\uFF09\",\n },\n prompt: { type: \"string\", description: \"\u81EA\u7531 prompt\uFF08\u4E0E task/feature \u4E09\u9009\u4E00\uFF09\" },\n feature: { type: \"string\", description: \".md \u529F\u80FD\u6E05\u5355\u8DEF\u5F84\uFF08\u4E0E task/prompt \u4E09\u9009\u4E00\uFF09\" },\n maxSteps: { type: \"number\", default: 30, description: \"\u6700\u5927 agent \u6B65\u6570\" },\n appHints: { type: \"string\", description: \"\u5E94\u7528\u5BFC\u822A\u63D0\u793A\" },\n serial: { type: \"string\", description: \"\u8BBE\u5907\u5E8F\u5217\u53F7\" },\n limit: { type: \"number\", description: \"--feature \u6A21\u5F0F\u4E0B\u53D6\u524D N \u6761\" },\n offset: { type: \"number\", default: 1, description: \"--feature \u6A21\u5F0F\u4E0B\u504F\u79FB\" },\n quiet: { type: \"boolean\", default: false, description: \"\u6291\u5236\u63D0\u793A\u6027\u8F93\u51FA\" },\n },\n required: [\"app\", \"package\"],\n },\n execute: async (args: Record<string, unknown>, context: ToolContext) => {\n const app = String(args.app ?? \"\")\n const pkg = String(args.package ?? \"\")\n if (!app || !pkg) {\n throw new Error(\"a2h_app_feature_verify: `app` and `package` are required\")\n }\n const task = args.task != null ? String(args.task) : null\n const prompt = args.prompt != null ? String(args.prompt) : null\n const feature = args.feature != null ? String(args.feature) : null\n if (!task && !prompt && !feature) {\n throw new Error(\"a2h_app_feature_verify: one of `task` / `prompt` / `feature` is required\")\n }\n\n // Resolve host model params \u2014 the apiKey lives only in this process's\n // memory from here on.\n const model = await deps.resolveModelParams(context.worktree)\n if (!model || !model.apiKey) {\n throw new Error(\"a2h_app_feature_verify: host did not provide model params (apiKey missing)\")\n }\n\n // Assemble argv from provided args; append --model-stdin so the script\n // reads model config from stdin rather than env/argv.\n const argv: string[] = [\"--app\", app, \"--package\", pkg, \"--device\", String(args.device ?? \"hdc\")]\n if (task) argv.push(\"--task\", task)\n if (prompt) argv.push(\"--prompt\", prompt)\n if (feature) argv.push(\"--feature\", feature)\n if (args.maxSteps != null) argv.push(\"--max-steps\", String(args.maxSteps))\n if (args.appHints != null) argv.push(\"--app-hints\", String(args.appHints))\n if (args.serial != null) argv.push(\"--serial\", String(args.serial))\n if (args.limit != null) argv.push(\"--limit\", String(args.limit))\n if (args.offset != null) argv.push(\"--offset\", String(args.offset))\n if (args.quiet === true) argv.push(\"--quiet\")\n argv.push(\"--model-stdin\")\n\n // Sanitize env: strip every var that could carry a model apiKey so the\n // child (and its adb/hdc descendants) cannot read it from the environment.\n const env: NodeJS.ProcessEnv = { ...process.env }\n for (const k of SECRET_ENV) delete env[k]\n\n // Spawn via PATH `node` (same binary the shell used to run the .ts script\n // \u2014 guaranteed \u2265 22.18 for native TS). process.execPath might be an older\n // host node, so prefer PATH resolution to match prior working behavior.\n const child = spawn(\"node\", [SCRIPT_PATH, ...argv], {\n cwd: context.directory,\n env,\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n })\n\n // Pipe the model config to the child's stdin and close it \u2014 the key\n // traverses only the OS pipe, never env/argv/disk.\n child.stdin.end(\n JSON.stringify({ apiKey: model.apiKey, modelName: model.modelName, baseURL: model.baseURL }),\n )\n\n let stdout = \"\"\n let stderr = \"\"\n let spawnError = \"\"\n const stepRe = /\u6267\u884C\u52A8\u4F5C:\\s*(.*)/g\n child.stdout.on(\"data\", (chunk: Buffer) => {\n const text = chunk.toString(\"utf8\")\n stdout += text\n // Surface the latest step's action as the tool's running title.\n const matches = [...text.matchAll(stepRe)]\n if (matches.length) {\n const last = matches[matches.length - 1][1].trim()\n try {\n context.metadata({ title: `UI verify: ${last.slice(0, 80)}` })\n } catch {\n /* host may not implement metadata \u2014 non-essential */\n }\n }\n })\n child.stderr.on(\"data\", (chunk: Buffer) => {\n stderr += chunk.toString(\"utf8\")\n })\n\n // Honor abort: kill the child if the host cancels the tool call.\n const onAbort = () => {\n if (!child.killed) child.kill(\"SIGTERM\")\n }\n context.abort.addEventListener(\"abort\", onAbort)\n\n const code: number = await new Promise((resolve) => {\n child.on(\"close\", resolve)\n child.on(\"error\", (err: Error) => {\n spawnError = err.message\n resolve(-1)\n })\n })\n context.abort.removeEventListener(\"abort\", onAbort)\n\n if (code !== 0) {\n if (spawnError) {\n throw new Error(`a2h_app_feature_verify: failed to spawn node \u2014 ${spawnError}`)\n }\n const tail = stderr.slice(-2000)\n throw new Error(`a2h_app_feature_verify failed (exit ${code}): ${tail || \"(no stderr)\"}`)\n }\n return { ok: true, exitCode: code, output: cap(stdout) }\n },\n }\n}\n", "import path from \"node:path\"\nimport type { A2HHostDeps, ToolContext, ToolDefinition } from \"../types\"\nimport { AUTOTEST_ROOT, autotestToolError, runAutotestScript } from \"./autotest-shared\"\n\nconst SCRIPT_REL = path.join(\"resolve-metadata-tool.ts\")\nconst TOOL_ID = \"a2h_autotest_resolve_metadata\"\n\n/**\n * Build the `a2h_autotest_resolve_metadata` plugin tool.\n *\n * Wraps `tools/autotest/resolve-metadata-tool.ts`: deterministically resolves\n * `bundle_name` / `app_name` from a HarmonyOS project's `AppScope/app.json5`\n * (resolving `$string:xxx` refs) and writes `app-metadata.json`. No model, no\n * device \u2014 pure metadata extraction.\n */\nexport function makeA2hAutotestResolveMetadataTool(_deps: A2HHostDeps): ToolDefinition {\n return {\n description:\n \"Resolve bundle_name / app_name from a HarmonyOS project (reads AppScope/app.json5 and resolves $string refs) and writes app-metadata.json. Pure metadata extraction \u2014 no model, no device. Use this instead of running `node resolve-metadata-tool.ts` directly. Returns the written metadata JSON path + stdout on success.\",\n args: {\n type: \"object\",\n properties: {\n projectDir: {\n type: \"string\",\n description: \"HarmonyOS \u5DE5\u7A0B\u6839\u76EE\u5F55\uFF08\u542B AppScope/app.json5\uFF09\",\n },\n output: {\n type: \"string\",\n description: \"app-metadata.json \u8F93\u51FA\u8DEF\u5F84\",\n },\n },\n required: [\"projectDir\", \"output\"],\n },\n execute: async (args: Record<string, unknown>, context: ToolContext) => {\n const projectDir = String(args.projectDir ?? \"\")\n const output = String(args.output ?? \"\")\n if (!projectDir || !output) {\n throw new Error(`${TOOL_ID}: \\`projectDir\\` and \\`output\\` are required`)\n }\n const argv = [\"--project-dir\", projectDir, \"--output\", output]\n const r = await runAutotestScript({\n scriptRel: SCRIPT_REL,\n argv,\n cwd: context.directory,\n abort: context.abort,\n })\n if (!r.ok) throw autotestToolError(TOOL_ID, r)\n return { ok: true, exitCode: r.exitCode, output: r.stdout }\n },\n }\n}\n", "import { spawn } from \"node:child_process\"\nimport path from \"node:path\"\nimport { PKG_ROOT } from \"../paths\"\n\n/**\n * Shared spawn harness for the a2h autotest plugin tools. Each tool wraps a\n * `node <tools/autotest/...>.ts` engine script; this centralizes the\n * boilerplate (PATH `node` for \u226522.18 native TS, secret-env strip, captured\n * stdio, abort, output cap) so the 4 tool files stay thin.\n */\nexport const AUTOTEST_ROOT = path.join(PKG_ROOT, \"tools\", \"autotest\")\n\n/**\n * Env vars that may carry a model apiKey. Stripped from the child's env so the\n * key is never inherited by the spawned script or its hdc/adb descendants. The\n * selftest `run` script instead receives the key via the stdin pipe\n * (`--model-stdin`).\n */\nconst SECRET_ENV = [\n \"HOMETRANS_MODEL_API_KEY\",\n \"HOMETRANS_MODEL_NAME\",\n \"HOMETRANS_MODEL_BASE_URL\",\n \"GLM_API_KEY\",\n \"TEST_API_KEY\",\n] as const\n\n/** Cap stdout returned to the host so a runaway loop can't overflow the chat. */\nexport function cap(s: string, max = 50000): string {\n if (s.length <= max) return s\n const half = Math.floor(max / 2)\n return s.slice(0, half) + `\\n\u2026[truncated ${s.length - max} chars]\u2026\\n` + s.slice(-half)\n}\n\nexport interface RunOpts {\n /** Path under tools/autotest/ to the engine .ts script, e.g. \"engine/self-test-runner.ts\". */\n scriptRel: string\n /** Argv after the script path (subcommand + flags). */\n argv: string[]\n /** Working directory (typically context.directory). */\n cwd: string\n /** Abort signal from the tool context. */\n abort: AbortSignal\n /** Optional stdin payload (e.g. model config JSON) \u2014 written then closed. */\n stdin?: string\n /** Optional progress hook for stdout chunks (surfaces a running title). */\n onStdoutChunk?: (text: string) => void\n}\n\nexport interface RunResult {\n ok: boolean\n exitCode: number\n stdout: string\n stderr: string\n spawnError: string\n}\n\n/**\n * Spawn `node <tools/autotest/...> argv` with secret env stripped, captured\n * stdio, and abort \u2192 SIGTERM the child. Resolves with a RunResult (never\n * rejects); callers decide ok/fail from `exitCode`).\n */\nexport function runAutotestScript(opts: RunOpts): Promise<RunResult> {\n return new Promise((resolve) => {\n const scriptPath = path.join(AUTOTEST_ROOT, opts.scriptRel)\n const env: NodeJS.ProcessEnv = { ...process.env }\n for (const k of SECRET_ENV) delete env[k]\n\n // PATH `node` (guaranteed \u2265 22.18 for native TS type-stripping); process.execPath\n // may be an older host node, so prefer PATH resolution (matches a2h_app_feature_verify).\n const child = spawn(\"node\", [scriptPath, ...opts.argv], {\n cwd: opts.cwd,\n env,\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n })\n\n let stdout = \"\"\n let stderr = \"\"\n let spawnError = \"\"\n\n child.stdout.on(\"data\", (chunk: Buffer) => {\n const text = chunk.toString(\"utf8\")\n stdout += text\n opts.onStdoutChunk?.(text)\n })\n child.stderr.on(\"data\", (chunk: Buffer) => {\n stderr += chunk.toString(\"utf8\")\n })\n\n // Write the stdin payload (or just close stdin) \u2014 the key travels only\n // through the OS pipe, never env/argv/disk.\n child.stdin.end(opts.stdin ?? \"\")\n\n const onAbort = () => {\n if (!child.killed) child.kill(\"SIGTERM\")\n }\n opts.abort.addEventListener(\"abort\", onAbort)\n\n child.on(\"close\", (code: number | null) => {\n opts.abort.removeEventListener(\"abort\", onAbort)\n resolve({ ok: code === 0, exitCode: code ?? -1, stdout: cap(stdout), stderr, spawnError })\n })\n child.on(\"error\", (err: Error) => {\n opts.abort.removeEventListener(\"abort\", onAbort)\n spawnError = err.message\n resolve({ ok: false, exitCode: -1, stdout: cap(stdout), stderr, spawnError })\n })\n })\n}\n\n/** Build a tool-shaped Error from a non-zero run result. */\nexport function autotestToolError(toolId: string, r: RunResult): Error {\n if (r.spawnError) return new Error(`${toolId}: failed to spawn node \u2014 ${r.spawnError}`)\n const tail = r.stderr.slice(-2000)\n return new Error(`${toolId} failed (exit ${r.exitCode}): ${tail || \"(no stderr)\"}`)\n}\n", "import path from \"node:path\"\nimport type { A2HHostDeps, ToolContext, ToolDefinition } from \"../types\"\nimport { autotestToolError, runAutotestScript } from \"./autotest-shared\"\n\nconst SCRIPT_REL = path.join(\"engine\", \"testcases-tool.ts\")\nconst TOOL_ID = \"a2h_autotest_testcases\"\n\n/**\n * Build the `a2h_autotest_testcases` plugin tool \u2014 the grouped entry point for\n * the testcases engine (`tools/autotest/engine/testcases-tool.ts`).\n *\n * Two actions, dispatched on `action`:\n * - `generate`: build a schema-compliant testcases.json from extractor output\n * ({ bundle_name, app_name, cases:[{case_name,actions,expected_results}] }).\n * Optional `validate` validates the generated file in place.\n * - `validate`: check an existing testcases.json against the schema.\n *\n * No model, no device. Transform convention \u2014 a non-zero exit is a real\n * failure: `autotestToolError` is thrown (only `generate`/`validate` are\n * transform ops, never state probes).\n */\nexport function makeA2hAutotestTestcasesTool(_deps: A2HHostDeps): ToolDefinition {\n return {\n description:\n \"HarmonyOS self-test testcases.json engine \u2014 grouped by noun, dispatched on `action`. `generate`: build a schema-compliant testcases.json from extractor output ({ bundle_name, app_name, cases:[{case_name,actions,expected_results}] }); pass `validate` to validate in place. `validate`: check an existing testcases.json against the schema. No model, no device. Non-zero exit is a failure (thrown). Use this instead of running `node testcases-tool.ts` directly.\",\n args: {\n type: \"object\",\n properties: {\n action: {\n type: \"string\",\n enum: [\"generate\", \"validate\"],\n description: \"generate=\u4ECE\u62BD\u53D6\u8F93\u51FA\u5EFA testcases.json\uFF1Bvalidate=\u6821\u9A8C\u5DF2\u6709 testcases.json\",\n },\n input: { type: \"string\", description: \"[generate] \u62BD\u53D6\u8F93\u51FA JSON \u8DEF\u5F84\" },\n output: { type: \"string\", description: \"[generate] testcases.json \u8F93\u51FA\u8DEF\u5F84\" },\n validate: { type: \"boolean\", default: false, description: \"[generate] \u5199\u5B8C\u540E\u5C31\u5730\u6821\u9A8C\" },\n path: { type: \"string\", description: \"[validate] \u8981\u6821\u9A8C\u7684 testcases.json \u8DEF\u5F84\" },\n },\n required: [\"action\"],\n },\n execute: async (args: Record<string, unknown>, context: ToolContext) => {\n const action = String(args.action ?? \"\")\n if (action === \"generate\") {\n const input = String(args.input ?? \"\")\n const output = String(args.output ?? \"\")\n if (!input || !output) {\n throw new Error(`${TOOL_ID}: \\`action:\"generate\"\\` requires \\`input\\` and \\`output\\``)\n }\n const argv = [\"generate\", input, output]\n if (args.validate === true) argv.push(\"--validate\")\n const r = await runAutotestScript({\n scriptRel: SCRIPT_REL,\n argv,\n cwd: context.directory,\n abort: context.abort,\n })\n if (!r.ok) throw autotestToolError(TOOL_ID, r)\n return { ok: true, exitCode: r.exitCode, output: r.stdout }\n }\n if (action === \"validate\") {\n const p = String(args.path ?? \"\")\n if (!p) {\n throw new Error(`${TOOL_ID}: \\`action:\"validate\"\\` requires \\`path\\``)\n }\n const argv = [\"validate\", p]\n const r = await runAutotestScript({\n scriptRel: SCRIPT_REL,\n argv,\n cwd: context.directory,\n abort: context.abort,\n })\n if (!r.ok) throw autotestToolError(TOOL_ID, r)\n return { ok: true, exitCode: r.exitCode, output: r.stdout }\n }\n throw new Error(`${TOOL_ID}: unknown \\`action\\` \"${action}\" (expected generate|validate)`)\n },\n }\n}\n", "import path from \"node:path\"\nimport type { A2HHostDeps, ToolContext, ToolDefinition } from \"../types\"\nimport { autotestToolError, runAutotestScript } from \"./autotest-shared\"\n\nconst SCRIPT_REL = path.join(\"engine\", \"report-tool.ts\")\nconst TOOL_ID = \"a2h_autotest_report\"\n\n/**\n * Build the `a2h_autotest_report` plugin tool \u2014 the grouped entry point for\n * the report engine (`tools/autotest/engine/report-tool.ts`).\n *\n * Two actions, dispatched on `action`:\n * - `generate`: render self-test-report.md from a task_<ts>/ directory\n * (summary.json + task_results.jsonl + per-case judgment logs). Optional\n * `validate` validates the rendered report in place.\n * - `validate`: check an existing self-test-report.md's layout.\n *\n * No model, no device. Transform convention \u2014 non-zero exit is a real failure\n * (`autotestToolError` thrown).\n */\nexport function makeA2hAutotestReportTool(_deps: A2HHostDeps): ToolDefinition {\n return {\n description:\n \"HarmonyOS self-test report engine \u2014 grouped by noun, dispatched on `action`. `generate`: render self-test-report.md from a task_<ts>/ directory (summary.json + task_results.jsonl + per-case judgment logs): overview, pre-cases, regular-cases, summary table, suggestions; pass `validate` to validate in place. `validate`: check an existing report.md's layout. No model, no device. Non-zero exit is a failure (thrown). Use this instead of running `node report-tool.ts` directly.\",\n args: {\n type: \"object\",\n properties: {\n action: {\n type: \"string\",\n enum: [\"generate\", \"validate\"],\n description: \"generate=\u4ECE task \u76EE\u5F55\u6E32\u67D3 self-test-report.md\uFF1Bvalidate=\u6821\u9A8C\u5DF2\u6709 report.md \u5E03\u5C40\",\n },\n taskSubdir: { type: \"string\", description: \"[generate] task_<ts>/ \u76EE\u5F55\u8DEF\u5F84\" },\n appMetadata: { type: \"string\", description: \"[generate] app-metadata.json \u8DEF\u5F84\" },\n hap: { type: \"string\", description: \"[generate] HAP \u6587\u4EF6\u8DEF\u5F84\" },\n device: { type: \"string\", description: \"[generate] \u8BBE\u5907\u6807\u8BC6\uFF08\u5982 127.0.0.1:5555\uFF09\" },\n suite: { type: \"string\", description: \"[generate] \u6D4B\u8BD5\u5957\u4EF6\u6807\u9898\" },\n out: { type: \"string\", description: \"[generate] self-test-report.md \u8F93\u51FA\u8DEF\u5F84\" },\n validate: { type: \"boolean\", default: false, description: \"[generate] \u5199\u5B8C\u540E\u5C31\u5730\u6821\u9A8C\" },\n path: { type: \"string\", description: \"[validate] \u8981\u6821\u9A8C\u7684 report.md \u8DEF\u5F84\" },\n },\n required: [\"action\"],\n },\n execute: async (args: Record<string, unknown>, context: ToolContext) => {\n const action = String(args.action ?? \"\")\n if (action === \"generate\") {\n const taskSubdir = String(args.taskSubdir ?? \"\")\n const appMetadata = String(args.appMetadata ?? \"\")\n const hap = String(args.hap ?? \"\")\n const device = String(args.device ?? \"\")\n const suite = String(args.suite ?? \"\")\n const out = String(args.out ?? \"\")\n if (!taskSubdir || !appMetadata || !hap || !device || !suite || !out) {\n throw new Error(\n `${TOOL_ID}: \\`action:\"generate\"\\` requires \\`taskSubdir\\`, \\`appMetadata\\`, \\`hap\\`, \\`device\\`, \\`suite\\`, \\`out\\``,\n )\n }\n const argv = [\n \"generate\",\n \"--task-subdir\", taskSubdir,\n \"--app-metadata\", appMetadata,\n \"--hap\", hap,\n \"--device\", device,\n \"--suite\", suite,\n \"--out\", out,\n ]\n if (args.validate === true) argv.push(\"--validate\")\n const r = await runAutotestScript({\n scriptRel: SCRIPT_REL,\n argv,\n cwd: context.directory,\n abort: context.abort,\n })\n if (!r.ok) throw autotestToolError(TOOL_ID, r)\n return { ok: true, exitCode: r.exitCode, output: r.stdout }\n }\n if (action === \"validate\") {\n const p = String(args.path ?? \"\")\n if (!p) {\n throw new Error(`${TOOL_ID}: \\`action:\"validate\"\\` requires \\`path\\``)\n }\n const argv = [\"validate\", p]\n const r = await runAutotestScript({\n scriptRel: SCRIPT_REL,\n argv,\n cwd: context.directory,\n abort: context.abort,\n })\n if (!r.ok) throw autotestToolError(TOOL_ID, r)\n return { ok: true, exitCode: r.exitCode, output: r.stdout }\n }\n throw new Error(`${TOOL_ID}: unknown \\`action\\` \"${action}\" (expected generate|validate)`)\n },\n }\n}\n", "import { spawn } from \"node:child_process\"\nimport path from \"node:path\"\nimport type { A2HHostDeps, ToolContext, ToolDefinition } from \"../types\"\nimport { AUTOTEST_ROOT, runAutotestScript } from \"./autotest-shared\"\n\nconst SCRIPT_REL = path.join(\"engine\", \"self-test-runner.ts\")\nconst TOOL_ID = \"a2h_autotest_selftest\"\n\n/**\n * Build the `a2h_autotest_selftest` plugin tool \u2014 the grouped entry point for\n * the self-test engine (`tools/autotest/engine/self-test-runner.ts`).\n *\n * Six actions, dispatched on `action`:\n * - `run`: hdc uninstall+install of the HAP, then spawn a detached\n * `@autotest/agent` batch. The multimodal model is resolved via the host's\n * `deps.resolveModelParams(worktree)` and piped through stdin\n * (`--model-stdin`) \u2014 the apiKey never touches env/argv/disk. Without\n * `timeout`, returns RUNNING JSON immediately (exit 2); with `timeout`,\n * blocks until terminal or timeout (0 COMPLETED / 1 FAILED / 3 CRASHED /\n * 4 NOT_STARTED / 5 TIMEOUT). Abort fires a detached `kill --task-dir`.\n * - `status`: probe on-disk state (summary.json / task_results.jsonl / pid\n * liveness). 0 COMPLETED / 2 RUNNING / 3 CRASHED / 4 NOT_STARTED.\n * - `kill`: terminate the batch tree via the pid file. NO_PID_FILE /\n * ALREADY_DEAD / KILLED.\n * - `check_hap`: validate a hap_path (every entry exists + \u22651 entry HAP).\n * - `check_inputs`: validate testcases.json parses to a non-empty array +\n * app-metadata.json has required fields.\n * - `check_pre`: assert [PRE]-prefixed cases are contiguous from index 0.\n *\n * All six use the STATE convention \u2014 exit codes are states/check results, NOT\n * failures: only a spawn error throws; otherwise the stdout JSON + exitCode\n * are returned for the caller to branch on (non-zero = a state or a failed\n * check, reason in stdout).\n *\n * Only `run` needs a model. The factory takes `deps` so the `run` branch can\n * call `deps.resolveModelParams`; the other five branches ignore `deps`.\n */\nexport function makeA2hAutotestSelftestTool(deps: A2HHostDeps): ToolDefinition {\n return {\n description:\n \"HarmonyOS on-device self-test engine \u2014 grouped by noun, dispatched on `action`. `run`: hdc uninstall+install of HAP, then a detached @autotest/agent batch (multimodal model resolved by the host, piped via stdin; never env/argv/disk). Without `timeout` returns RUNNING immediately (exit 2); with `timeout` blocks until terminal (0 COMPLETED / 1 FAILED / 3 CRASHED / 4 NOT_STARTED / 5 TIMEOUT). `status`: probe on-disk state (0/2/3/4). `kill`: tear down the batch tree. `check_hap`/`check_inputs`/`check_pre`: pre-flight validators (0 = passed). Exit codes are states, not errors \u2014 branch on `status` in the returned JSON. Only `run` needs a model. Use this instead of running `node self-test-runner.ts` directly.\",\n args: {\n type: \"object\",\n properties: {\n action: {\n type: \"string\",\n enum: [\"run\", \"status\", \"kill\", \"check_hap\", \"check_inputs\", \"check_pre\"],\n description:\n \"run=\u88C5 HAP + \u8DD1 batch\uFF08\u9700\u6A21\u578B\uFF09\uFF1Bstatus=\u63A2\u72B6\u6001\uFF1Bkill=\u7EC8\u6B62 batch\uFF1Bcheck_hap=\u6821\u9A8C hap_path\uFF1Bcheck_inputs=\u6821\u9A8C testcases+metadata\uFF1Bcheck_pre=\u6821\u9A8C [PRE] \u8FDE\u7EED\",\n },\n // \u2500\u2500 run \u2500\u2500\n testcases: { type: \"string\", description: \"[run/check_inputs/check_pre] \u6D4B\u8BD5\u7528\u4F8B\u6587\u4EF6\uFF08JSON \u6570\u7EC4\u6216 JSONL\uFF09\" },\n hap: { type: \"string\", description: \"[run/check_hap] \u7B7E\u540D\u5305\uFF08.hap/.hsp \u6587\u4EF6\u3001\u76EE\u5F55\uFF0C\u6216\u9017\u53F7\u5206\u9694\u591A\u9879\uFF09\" },\n bundleName: { type: \"string\", description: \"[run] \u5305\u540D\uFF08bundle_name\uFF09\" },\n taskDir: {\n type: \"string\",\n description: \"[run/status/kill] \u4EFB\u52A1\u76EE\u5F55\uFF08run \u5185\u90E8\u5EFA task_<ts> \u5B50\u76EE\u5F55\uFF1B\u4E0D\u4F20\u5219\u9ED8\u8BA4 'task'\uFF09\u3002status/kill \u636E\u6B64\u627E batch.pid\",\n },\n outputDir: { type: \"string\", description: \"[run/status] \u65E5\u5FD7\u76EE\u5F55\uFF08\u4E0D\u4F20\u5219\u540C taskDir\uFF09\" },\n category: { type: \"string\", description: \"[run] \u6D4B\u8BD5\u5206\u7C7B\u540D\uFF0C\u4F20\u7ED9 batch-launcher\" },\n timeout: {\n type: [\"number\", \"string\"],\n description: \"[run] \u963B\u585E\u81F3\u7EC8\u6001\u6216\u8D85\u65F6\u3002\u6570\u503C=\u79D2\u6570\uFF1B\\\"auto\\\"=\u5F15\u64CE\u89E3\u6790 testcases \u540E\u6309\u7528\u4F8B\u6570\u00D7720s \u81EA\u52A8\u63A8\u5BFC\uFF08\u6BCF\u4F8B 10min + 20% \u4F59\u91CF\uFF09\u3002\u4E0D\u4F20\u5219\u7ACB\u5373\u8FD4\u56DE RUNNING\uFF1B\u8D85\u65F6\u540E\u81EA\u52A8 kill batch \u5E76\u8FD4\u56DE TIMEOUT\uFF08exit 5\uFF09\",\n },\n deviceSn: { type: \"string\", description: \"[run] \u8BBE\u5907\u5E8F\u5217\u53F7\uFF08\u2192 AutoTestAgent.deviceSn\uFF09\" },\n ip: { type: \"string\", description: \"[run] \u8BBE\u5907 IP\uFF08\u2192 AutoTestAgent.ip\uFF09\" },\n port: { type: \"string\", description: \"[run] \u8BBE\u5907\u7AEF\u53E3\uFF08\u2192 AutoTestAgent.port\uFF09\" },\n agentMode: { type: \"string\", description: \"[run] agent \u6A21\u5F0F\uFF08\u9ED8\u8BA4 single\uFF09\" },\n maxSteps: { type: \"number\", description: \"[run] \u5355\u7528\u4F8B\u6700\u5927\u6B65\u6570\" },\n promptVersion: { type: \"string\", description: \"[run] prompt \u7248\u672C\uFF08\u9ED8\u8BA4 v2.0\uFF09\" },\n // \u2500\u2500 check_inputs \u2500\u2500\n metadata: { type: \"string\", description: \"[check_inputs] app-metadata.json \u8DEF\u5F84\" },\n },\n required: [\"action\"],\n },\n execute: async (args: Record<string, unknown>, context: ToolContext) => {\n const action = String(args.action ?? \"\")\n\n // \u2500\u2500 run \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (action === \"run\") {\n const testcases = String(args.testcases ?? \"\")\n const hap = String(args.hap ?? \"\")\n const bundleName = String(args.bundleName ?? \"\")\n if (!testcases || !hap || !bundleName) {\n throw new Error(`${TOOL_ID}: \\`action:\"run\"\\` requires \\`testcases\\`, \\`hap\\`, \\`bundleName\\``)\n }\n // Effective taskDir drives both argv and the abort-cleanup kill spawn.\n // self-test-runner defaults to 'task' when --task-dir is absent, so mirror that.\n const taskDir = String(args.taskDir ?? \"task\")\n\n const argv = [\n \"run\",\n \"--testcases\", testcases,\n \"--hap\", hap,\n \"--bundle-name\", bundleName,\n \"--task-dir\", taskDir,\n \"--model-stdin\",\n ]\n if (args.outputDir) argv.push(\"--output-dir\", String(args.outputDir))\n if (args.category) argv.push(\"--category\", String(args.category))\n if (args.deviceSn) argv.push(\"--device-sn\", String(args.deviceSn))\n if (args.ip) argv.push(\"--ip\", String(args.ip))\n if (args.port) argv.push(\"--port\", String(args.port))\n if (args.agentMode) argv.push(\"--agent-mode\", String(args.agentMode))\n if (args.maxSteps !== undefined && args.maxSteps !== null) {\n argv.push(\"--max-steps\", String(args.maxSteps))\n }\n if (args.promptVersion) argv.push(\"--prompt-version\", String(args.promptVersion))\n if (args.timeout !== undefined && args.timeout !== null) {\n argv.push(\"--timeout\", String(args.timeout))\n }\n\n // Resolve the model via the host; null = host can't supply one \u2014 fail\n // fast rather than spawning a batch that dies mid-flight for lack of creds.\n const model = await deps.resolveModelParams(context.worktree)\n if (!model) {\n throw new Error(\n `${TOOL_ID}: \\`action:\"run\"\\` needs a model but deps.resolveModelParams returned null ` +\n `for worktree ${context.worktree ?? \"(none)\"} \u2014 configure the host's model provider`,\n )\n }\n const stdin = JSON.stringify({\n apiKey: model.apiKey,\n modelName: model.modelName,\n baseURL: model.baseURL,\n })\n\n // Abort cleanup: the detached batch survives SIGTERM of the\n // self-test-runner child, so on abort also fire `kill --task-dir`.\n // Fire-and-forget; best-effort.\n const onAbortKill = (): void => {\n try {\n const env: NodeJS.ProcessEnv = { ...process.env }\n // strip secrets defensively (kill needs no model)\n delete env.HOMETRANS_MODEL_API_KEY\n delete env.HOMETRANS_MODEL_NAME\n delete env.HOMETRANS_MODEL_BASE_URL\n delete env.GLM_API_KEY\n delete env.TEST_API_KEY\n const k = spawn(\n \"node\",\n [path.join(AUTOTEST_ROOT, SCRIPT_REL), \"kill\", \"--task-dir\", taskDir],\n { cwd: context.directory, env, stdio: \"ignore\", detached: true, windowsHide: true },\n )\n k.on(\"error\", () => { /* best effort \u2014 swallow */ })\n k.unref()\n } catch {\n // best effort\n }\n }\n if (!context.abort.aborted) {\n context.abort.addEventListener(\"abort\", onAbortKill)\n }\n\n const r = await runAutotestScript({\n scriptRel: SCRIPT_REL,\n argv,\n cwd: context.directory,\n abort: context.abort,\n stdin,\n })\n\n // Remove our abort listener; the shared harness removes its own on close.\n context.abort.removeEventListener(\"abort\", onAbortKill)\n\n if (r.spawnError) {\n throw new Error(`${TOOL_ID}: failed to spawn node \u2014 ${r.spawnError}`)\n }\n // Exit codes are states, not failures \u2014 surface the JSON + code.\n return { ok: r.exitCode === 0, exitCode: r.exitCode, output: r.stdout }\n }\n\n // \u2500\u2500 status \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (action === \"status\") {\n const taskDir = String(args.taskDir ?? \"\")\n if (!taskDir) {\n throw new Error(`${TOOL_ID}: \\`action:\"status\"\\` requires \\`taskDir\\``)\n }\n const argv = [\"status\", \"--task-dir\", taskDir]\n if (args.outputDir) argv.push(\"--output-dir\", String(args.outputDir))\n const r = await runAutotestScript({\n scriptRel: SCRIPT_REL,\n argv,\n cwd: context.directory,\n abort: context.abort,\n })\n if (r.spawnError) {\n throw new Error(`${TOOL_ID}: failed to spawn node \u2014 ${r.spawnError}`)\n }\n return { ok: r.exitCode === 0, exitCode: r.exitCode, output: r.stdout }\n }\n\n // \u2500\u2500 kill \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (action === \"kill\") {\n const taskDir = String(args.taskDir ?? \"\")\n if (!taskDir) {\n throw new Error(`${TOOL_ID}: \\`action:\"kill\"\\` requires \\`taskDir\\``)\n }\n const argv = [\"kill\", \"--task-dir\", taskDir]\n const r = await runAutotestScript({\n scriptRel: SCRIPT_REL,\n argv,\n cwd: context.directory,\n abort: context.abort,\n })\n if (r.spawnError) {\n throw new Error(`${TOOL_ID}: failed to spawn node \u2014 ${r.spawnError}`)\n }\n return { ok: r.exitCode === 0, exitCode: r.exitCode, output: r.stdout }\n }\n\n // \u2500\u2500 check_hap \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (action === \"check_hap\") {\n const hap = String(args.hap ?? \"\")\n if (!hap) {\n throw new Error(`${TOOL_ID}: \\`action:\"check_hap\"\\` requires \\`hap\\``)\n }\n const argv = [\"check-hap\", \"--hap\", hap]\n const r = await runAutotestScript({\n scriptRel: SCRIPT_REL,\n argv,\n cwd: context.directory,\n abort: context.abort,\n })\n if (r.spawnError) {\n throw new Error(`${TOOL_ID}: failed to spawn node \u2014 ${r.spawnError}`)\n }\n return { ok: r.exitCode === 0, exitCode: r.exitCode, output: r.stdout }\n }\n\n // \u2500\u2500 check_inputs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (action === \"check_inputs\") {\n const testcases = String(args.testcases ?? \"\")\n const metadata = String(args.metadata ?? \"\")\n if (!testcases || !metadata) {\n throw new Error(`${TOOL_ID}: \\`action:\"check_inputs\"\\` requires \\`testcases\\` and \\`metadata\\``)\n }\n const argv = [\"check-inputs\", \"--testcases\", testcases, \"--metadata\", metadata]\n const r = await runAutotestScript({\n scriptRel: SCRIPT_REL,\n argv,\n cwd: context.directory,\n abort: context.abort,\n })\n if (r.spawnError) {\n throw new Error(`${TOOL_ID}: failed to spawn node \u2014 ${r.spawnError}`)\n }\n return { ok: r.exitCode === 0, exitCode: r.exitCode, output: r.stdout }\n }\n\n // \u2500\u2500 check_pre \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (action === \"check_pre\") {\n const testcases = String(args.testcases ?? \"\")\n if (!testcases) {\n throw new Error(`${TOOL_ID}: \\`action:\"check_pre\"\\` requires \\`testcases\\``)\n }\n const argv = [\"check-pre\", \"--testcases\", testcases]\n const r = await runAutotestScript({\n scriptRel: SCRIPT_REL,\n argv,\n cwd: context.directory,\n abort: context.abort,\n })\n if (r.spawnError) {\n throw new Error(`${TOOL_ID}: failed to spawn node \u2014 ${r.spawnError}`)\n }\n return { ok: r.exitCode === 0, exitCode: r.exitCode, output: r.stdout }\n }\n\n throw new Error(\n `${TOOL_ID}: unknown \\`action\\` \"${action}\" (expected run|status|kill|check_hap|check_inputs|check_pre)`,\n )\n },\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["path", "path", "path", "path", "path", "path", "spawn", "path", "path", "SECRET_ENV", "cap", "spawn", "path", "path", "SCRIPT_REL", "path", "TOOL_ID", "path", "SCRIPT_REL", "path", "TOOL_ID", "spawn", "path", "SCRIPT_REL", "path", "TOOL_ID", "spawn", "path"]
7
7
  }
package/package.json CHANGED
@@ -1,29 +1,37 @@
1
- {
2
- "name": "android2harmony",
3
- "version": "0.1.2",
4
- "description": "A2H (Android-to-HarmonyOS) Plugin for DevEco Code — standalone plugin containing A2H skills, agents, and tools for converting Android apps to HarmonyOS",
5
- "type": "module",
6
- "license": "MIT",
7
- "publishConfig": {
8
- "registry": "https://registry.npmjs.org/"
9
- },
10
- "main": "./dist/index.js",
11
- "exports": {
12
- ".": "./dist/index.js"
13
- },
14
- "files": [
15
- "dist",
16
- "skills",
17
- "agents"
18
- ],
19
- "scripts": {
20
- "build": "node scripts/build.js",
21
- "dev": "tsc --watch"
22
- },
23
- "devDependencies": {
24
- "@types/node": "^22.0.0",
25
- "commander": "^14.0.0",
26
- "esbuild": "^0.25.0",
27
- "typescript": "^5.4.5"
28
- }
29
- }
1
+ {
2
+ "name": "android2harmony",
3
+ "version": "0.1.3",
4
+ "description": "A2H (Android-to-HarmonyOS) Plugin for DevEco Code — standalone plugin containing A2H skills, agents, and tools for converting Android apps to HarmonyOS",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "publishConfig": {
8
+ "registry": "https://registry.npmjs.org/"
9
+ },
10
+ "main": "./dist/index.js",
11
+ "exports": {
12
+ ".": "./dist/index.js"
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "skills",
17
+ "agents",
18
+ "tools"
19
+ ],
20
+ "scripts": {
21
+ "build": "node scripts/build.js",
22
+ "dev": "tsc --watch"
23
+ },
24
+ "engines": {
25
+ "node": ">=22.18.0"
26
+ },
27
+ "dependencies": {
28
+ "@autotest/agent": "file:tools/autotest/deps/autotest-agent-0.1.1.tgz",
29
+ "commander": "^14.0.0",
30
+ "jsonc-parser": "^3.3.1"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^22.0.0",
34
+ "esbuild": "^0.25.0",
35
+ "typescript": "^5.4.5"
36
+ }
37
+ }
@@ -0,0 +1,251 @@
1
+ # hmos-ui-align
2
+
3
+ HarmonyOS-Android UI 自动对齐流水线。
4
+
5
+ ## 它解决什么问题
6
+
7
+ 以往对齐鸿蒙和安卓 UI 的流程需要:
8
+ 1. 人工把设备点到目标页面
9
+ 2. 手动跑 parse 脚本截图 + dump view tree
10
+ 3. 肉眼对比差异
11
+ 4. 手写改鸿蒙源码
12
+
13
+ 每多一个页面/弹窗/tab,上述步骤都要重复一遍,非常费时。
14
+
15
+ 本 skill 做了三件事:
16
+ - 自动**寻路**(视觉模型根据自然语言点到指定页面,模型复用 `~/.hometrans/config.json` 统一配置)
17
+ - 自动**采集** view tree + 截图(安卓走 adb、鸿蒙走 hdc)
18
+ - 自动**对比 + 改码**,并按 MVVM 模式把 mock 数据放到 Model 层
19
+
20
+ 用户只需要一句自然语言 + 点击路径。
21
+
22
+ > ⚠️ 当前只保证 **UI 对齐**,不保证功能行为对齐。
23
+
24
+ ---
25
+
26
+ ## 前置条件
27
+
28
+ 1. **设备连接**
29
+ - 安卓设备:已装目标 App,`adb devices` 能看到
30
+ - 鸿蒙设备:已装目标 App,`hdc list targets` 能看到
31
+ - 两台设备建议都保持亮屏解锁
32
+
33
+ 2. **运行时依赖**
34
+ - 采集脚本 `page_capture.ts` 与寻路脚本 `app_feature_verify.ts` 均为 TypeScript 版,用 Node 直接运行(Node ≥ 22.18 或 ≥ 23.6,原生剥离类型,无需编译、零第三方依赖)。确认 `node --version` 即可,无需 Python / uv。
35
+
36
+ 3. **模型配置**(寻路导航用)
37
+ 与自测共用同一个多模态模型。插件场景下由 `a2h_app_feature_verify` 工具从 host 解析 key 并经 stdin 注入 `app_feature_verify.ts`(`--model-stdin`),key 不进环境变量、不泄露给 adb/hdc 子进程,无需单独配置。直接 `node` 运行(调试)时:`ht init` 把模型四要素写入 `~/.hometrans/config.json` 的 `autotest.unified_model`(并导出 `HOMETRANS_MODEL_*` 环境变量),脚本优先读 stdin → `--api-key` → 环境变量 → config.json;都没有时兼容老版本 `GLM_API_KEY`(走智谱 GLM 端点)。
38
+
39
+ 4. **鸿蒙 SDK 路径**(给改码阶段查 API 用)
40
+ 取自 OS 环境变量 `OHOS_SDK_PATH` / `HMS_SDK_PATH`(为空时由 `DEVECO_SDK_HOME` 派生),由 `ht init` 写入机器环境变量;例如 DevEco Studio 自带的 `E:\DevEco Studio\sdk\default\openharmony\ets`。
41
+
42
+ ---
43
+
44
+ ## 入参与配置
45
+
46
+ 本 skill 需要两类信息:
47
+
48
+ **① 调用时传入的入参**(每次按需提供):
49
+
50
+ | 入参 | 必填 | 说明 |
51
+ |---|---|--------------------------------------------------------------------|
52
+ | `android_project_dir` | 是 | 安卓源码根目录 |
53
+ | `harmony_project_dir` | 是 | 鸿蒙工程根目录(会被直接修改) |
54
+ | `capture_output_dir` | 否 | 采集产物输出目录(截图/view tree/分析报告)。默认 `<harmony_project_dir>/.hometrans/capture_output` |
55
+
56
+ **② 全局配置**(统一链路「环境变量 → `~/.hometrans/config.json` → 询问用户」,由 `ht init` 同时写入机器环境变量与 config.json,无需每次传):
57
+
58
+ - 模型配置 ← 插件场景由 `a2h_app_feature_verify` 工具经 stdin 注入(不经环境变量);直接 `node` 运行时读 `HOMETRANS_MODEL_*` 环境变量,缺失回落 `~/.hometrans/config.json` 的 `autotest.unified_model`(与自测共用同一个模型)
59
+ - 鸿蒙 SDK ETS API 目录 ← 环境变量 `OHOS_SDK_PATH` / `HMS_SDK_PATH`(为空时由 `DEVECO_SDK_HOME` 派生为 `<DEVECO_SDK_HOME>/default/openharmony/ets`、`.../hms/ets`)
60
+
61
+ > **Step 0.0** 会在执行前检查这些环境变量是否存在;缺失时会要求用户输入。
62
+
63
+ > 安卓/鸿蒙两端的 **App 显示名**(`android.app_name` / `harmony.app_name`)和 **包名**(`android.package` / `harmony.package`)也无需提供——skill 的 **Step 0.1** 会按 `android_project_dir` / `harmony_project_dir` 自动解析:
64
+ > - 安卓包名取自 app 模块 `build.gradle(.kts)` 的 `applicationId`(兜底 `AndroidManifest.xml` 的 `package`);App 名取自启动 Activity / `<application>` 的 `android:label`(`@string/xxx` 会去 `res/values*/strings.xml` 解析,优先中文)。
65
+ > - 鸿蒙包名取自 `AppScope/app.json5` 的 `bundleName`;App 名取自 `app.json5` 的 `label`(`$string:xxx` 会去 `AppScope/resources/**/element/string.json` 解析,优先中文)。
66
+ >
67
+ > 若某项无法从工程目录解析,skill 会回到询问用户。
68
+
69
+ ---
70
+
71
+ ## 使用方式
72
+
73
+ 在 Claude Code 里直接调用 skill,**把两个工程目录附在需求里**(可作为命名参数,也可在自然语言里说明):
74
+
75
+ ```
76
+ /hmos-ui-align android_project_dir: <安卓工程绝对路径> harmony_project_dir: <鸿蒙工程绝对路径>
77
+ <自然语言需求,描述要对齐的页面+点击路径>
78
+ ```
79
+
80
+ ### 写需求的三个要点
81
+
82
+ 1. **页面路径写清楚**
83
+ 用「→」或「-」标注一级一级的点击步骤,比如 `首页 → 点击"AI智能填报" → 点击"专业"筛选按钮`。路径越具体,寻路成功率越高。
84
+
85
+ 2. **说明是否要覆盖交互态**
86
+ 默认会自动扫描 tab、弹窗、下拉等交互元素并逐个采集。如果只想对齐主页面,请明确说「只对齐主页面,不管弹窗和 tab」。
87
+
88
+ 3. **说明是否用 Mock 数据**
89
+ 默认用 mock 数据。如果想调真实后台,在需求里写「不要 mock 数据,调用真实后台」。
90
+
91
+ ### 例子
92
+
93
+ **例 1(单个弹窗对齐)**
94
+ ```
95
+ /hmos-ui-align 鸿蒙版本app(登录状态下,进入首页-点击"AI智能填报"-点击"专业"筛选按钮)
96
+ 得到的弹窗样式和安卓同样路径得到的页面不一致,请修改鸿蒙源码将上述页面与安卓版本完全对齐
97
+ ```
98
+
99
+ **例 2(补齐缺失页面 + 调真实后台)**
100
+ ```
101
+ /hmos-ui-align 鸿蒙版本app(点击我的-超级会员组件)显示与安卓不一致,且安卓版本在超级会员
102
+ 上点击"会员中心"会跳到超级会员弹窗页,鸿蒙也没有这页。请修改鸿蒙源码将这些页面与安卓版本
103
+ 完全对齐,不要mock数据,调用真实后台数据
104
+ ```
105
+
106
+ **例 3(多级路径 + 多个页面)**
107
+ ```
108
+ /hmos-ui-align 鸿蒙版本(点击底部高考按钮 -> 带年纪查专业 -> 点击某一具体专业(如临床医学)
109
+ -> 点击就业分析,到达"专业就业健康度"展示页面)以及在"专业就业健康度"页面上点击"完整数
110
+ 据指标"到达的就业健康度详情页都和安卓同一路径的不一致。比较这些页面的差别并将他们在视觉
111
+ 效果上完全对齐,不要用mock数据,调用后台真实逻辑
112
+ ```
113
+
114
+ ---
115
+
116
+ ## 运行过程中会发生什么
117
+
118
+ skill 会严格按 `SKILL.md` 里的流水线跑:
119
+
120
+ ### Step 0 · 解析入参
121
+ 读取调用入参 `android_project_dir` / `harmony_project_dir` / `capture_output_dir`,并按统一链路「环境变量 → config.json → 询问」解析 `HOMETRANS_MODEL_*`(统一模型,回落 `~/.hometrans/config.json` 的 `autotest.unified_model`)与 `OHOS_SDK_PATH` / `HMS_SDK_PATH`(回落 config.json 的 `env.*`;**Step 0.0** 会先做存在性检查,两层都缺失才要求用户输入)。随后 **Step 0.1** 按两个工程目录自动解析两端的 App 显示名与包名(无需手动配置)。
122
+
123
+ ### Step 1 · 双端页面采集
124
+ 1.1 解析你的需求,拆成一组「基础页」,每个基础页有 `android_nav_path` 和 `hmos_nav_path`
125
+ 1.2 对每个基础页,两端分别:`app_feature_verify.ts` 寻路 → 成功后 `page_capture.ts` 截图 + dump view tree
126
+ 1.3 扫描基础页 view tree,发现 tab、弹窗、下拉等交互元素,自动对每个子状态再采集一遍
127
+ 1.4 扫描安卓源码里的动画/Toast/语音传感器等 API,产出 `dynamic_ui_inventory.md`(截图和 view tree 抓不到这些效果)
128
+ 1.5 识别多状态组件(如语音输入的 idle/recording/cancelling),读源码建 `state_model.md`,用 `page_capture_burst.ts` 连续采帧捕捉每个状态的代表帧
129
+ 1.6 鸿蒙端页面不存在时,寻路会失败,对应目录留空(Step 2 会改从源码读)
130
+
131
+ 产物目录结构:
132
+ ```
133
+ {capture_output_dir}/
134
+ task_{timestamp}/
135
+ android_page_1_{name}/
136
+ screenshot.png
137
+ view_tree.xml
138
+ meta.json # 含 dpi / density_factor,供换算用
139
+ hmos_page_1_{name}/
140
+ screenshot.png
141
+ view_tree.xml
142
+ meta.json
143
+ android_page_1_{name}_popup_filter/
144
+ ...
145
+ dynamic_ui_inventory.md # Step 1.4 产物
146
+ state_model.md # Step 1.5 产物(仅多状态组件存在时)
147
+ ```
148
+
149
+ ### Step 2 · UI 差异分析
150
+ 主 agent 串行执行,不会下放给子 agent(保证质量):
151
+ - **2.1** 读安卓 screenshot + view tree,写 `android_page_*/UI_Analysis.md`(组件清单 + 位置 / 颜色 / icon / 尺寸 / 对齐方式等),并附加 `dynamic_ui_inventory.md` 里与该页相关的动态效果
152
+ - **2.2** 读鸿蒙 screenshot + view tree,写 `hmos_page_*/UI_Analysis.md`;鸿蒙页不存在时改读源码写 `UI_Analysis_from_code.md`
153
+ - **2.3** 两份 Analysis 对比,按 `references/Comparison_Template.md` 生成 `UI_comparison.md`(静态组件 diff 表 + 动态UI diff 表 + 状态机 diff 表)
154
+ - **2.4** 验证所有 `UI_Analysis*.md` 和 `UI_comparison.md` 都已写全
155
+
156
+ 所有尺寸都会以 `126px (3.0x → 42vp)` 的格式同时给出原始 px、**从各自 `meta.json` 读到的真实设备密度**、换算后的 vp(不再假设固定 3x)。
157
+
158
+ ### Step 3 · 改鸿蒙源码
159
+ - 跑 `node ./scripts/extract_checklist.ts --task-dir "{task_dir}"` 确定性地从所有 `UI_comparison.md` 抽取 diff 项,生成 `{task_dir}/fix_checklist.md`(唯一 source of truth),并核对脚本输出的 `items >= diff_rows` 对账报告,避免人工枚举漏项
160
+ - 先探测工程状态管理范式(V1/V2,工程级判定一次):新工程/0→1 转换默认 V2,已有 V1 的工程沿用 V1
161
+ - 按范式读对应文档:V1 读 `references/MVVM开发文档/`,V2 读 `references/MVVM开发文档V2/`(详见 `references/MVVM开发文档V2/_范式选择说明.md`)
162
+ - 读 `page_align.md` 学习转换规则
163
+ - 逐条修 diff,每修一个把 `- [ ]` 改成 `- [x]`;`[STATE]`/`[TRANSITION]`/`[ANIMATION]` 类条目需按 `state_model.md` 精确复制手势阈值和动画参数
164
+ - 每个尺寸 / icon / alignment 都要回溯到安卓源码的 XML 或资源值,不允许"看起来差不多"
165
+
166
+ ### Step 4 · 编译校验
167
+ 若可用,调 `hmos-fix-build-errors` skill 确保工程能编过。**不会自动部署**。
168
+
169
+ ---
170
+
171
+ ## 目录结构
172
+
173
+ ```
174
+ skills/hmos-incremental-ui-align/
175
+ ├── SKILL.md # 流水线定义(主 agent 执行逻辑)
176
+ ├── README.md # 本文档
177
+ ├── page_align.md # Step 3 的转换规则
178
+ ├── diff_analysis.md # 内部说明
179
+ ├── scripts/
180
+ │ ├── app_feature_verify.ts # 寻路工具(TypeScript,Node 直跑)
181
+ │ ├── page_capture.ts # view tree + 截图采集工具(TypeScript,Node 直跑,含密度换算)
182
+ │ ├── page_capture_burst.ts # 连续帧采集工具(多状态组件用,TypeScript,Node 直跑)
183
+ │ ├── extract_checklist.ts # 确定性 diff→checklist 抽取工具(TypeScript,Node 直跑)
184
+ │ └── navigation-capure.md # 各脚本的调用约定
185
+ └── references/
186
+ ├── UI_Analysis_Template.md # Step 2 分析模板(含动态UI/多状态章节)
187
+ ├── Comparison_Template.md # Step 2.3 对比模板(含动态UI/状态机对比章节)
188
+ ├── State_Model_Template.md # Step 1.5 状态机建模模板
189
+ ├── MVVM开发文档/ # 鸿蒙 MVVM 参考(V1 状态管理)
190
+ ├── MVVM开发文档V2/ # 鸿蒙 MVVM 参考(V2 状态管理 + 范式选择说明)
191
+ ├── android-to-harmonyOS-ui-layout-mapping-reference.md
192
+ ├── android-to-harmonyOS-ui-atomic-component-mapping-reference.md
193
+ └── android-to-harmonyOS-ui-interaction-mapping-reference.md
194
+ ```
195
+
196
+ ---
197
+
198
+ ## 单独运行脚本(调试用)
199
+
200
+ 跳过 skill 手动跑采集(直接 `node` 运行不经工具,模型 key 需来自 `~/.hometrans/config.json` 的 `autotest.unified_model` 或 `HOMETRANS_MODEL_*` 环境变量;插件场景应改用 `a2h_app_feature_verify` 工具,key 经 stdin 注入不泄露):
201
+
202
+ ```powershell
203
+ # 安卓寻路(TypeScript 版,Node ≥ 22.18/23.6 直跑,无需 uv / PYTHONIOENCODING)
204
+ node Agents/hmos-ui-align/scripts/app_feature_verify.ts `
205
+ --device adb `
206
+ --app "Salt Player" `
207
+ --package "com.salt.music" `
208
+ --prompt "进入首页-点击AI智能填报-点击专业筛选按钮" `
209
+ --max-steps 15
210
+
211
+ # 安卓采集(TypeScript 版,Node ≥ 22.18/23.6 直跑,无需 uv / PYTHONIOENCODING)
212
+ node Agents/hmos-ui-align/scripts/page_capture.ts --device adb -o ./tmp/android_page_1_xxx
213
+
214
+ # 鸿蒙同理,把 --device adb 换成 --device hdc
215
+ ```
216
+
217
+ > 注意:`--prompt` 模式会自动在前面加「打开{app_name},」,所以 prompt 里不要再写"打开"。
218
+
219
+ ---
220
+
221
+ ## 常见问题
222
+
223
+ **Q: 寻路失败怎么办?**
224
+ A: skill 默认重试两次。流水线的内部规则:①先看页面是否存在;②路径错了就 force-stop 重试;③路径对但工具报错就让 agent 自己看截图判断是否到了。超过两次仍失败才会跳过该页。
225
+
226
+ **Q: 鸿蒙端页面根本不存在,能新建吗?**
227
+ A: 可以。Step 2.2 会改从鸿蒙源码读,Step 3 会按安卓的实现规格新建 `.ets` 文件到 `entry/src/main/ets/pages/`,并登记路由。
228
+
229
+ **Q: 为什么同一次任务会采集多个页面?**
230
+ A: Step 1.3 默认扫描交互元素(tab / 弹窗 / 展开收起等)并递归采集。想关掉请在需求里写「只对齐主页面」。
231
+
232
+ **Q: 改完会自动部署吗?**
233
+ A: 不会。Step 4 只跑编译验证(如果 `hmos-fix-build-errors` skill 可用)。部署自己来。
234
+
235
+ **Q: 能不能只做差异分析、不改码?**
236
+ A: 目前流水线是端到端的。如果只想要分析产物,跑完 Step 2 后手动中断即可,所有 `UI_Analysis.md` 和 `UI_comparison.md` 都会落盘在 `{capture_output_dir}/task_{timestamp}/` 下。
237
+
238
+ **Q: 动画、Toast 这类瞬态效果能对齐吗?**
239
+ A: 能。Step 1.4 会扫描安卓源码里的动画/Toast/语音传感器 API,产出 `dynamic_ui_inventory.md`;Step 2.3 会生成对应的「Dynamic / Transient UI Comparison」diff 表;Step 3 逐条修复时会写入真实的 ArkUI `animateTo()` / `promptAction.showToast()` 等实现。
240
+
241
+ **Q: 像"按住说话"这种有多个状态的组件怎么对齐?**
242
+ A: Step 1.5 会先建 `state_model.md`(状态枚举 + 转移条件 + 每状态属性,均需溯源到源码行号),再用 `page_capture_burst.ts` 连续采帧抓每个状态的代表帧。单击、松开长按等手势可自动触发;长按保持/拖拽/语音等手势需要人工在设备上操作,skill 会打印操作说明等你确认。
243
+
244
+ ---
245
+
246
+ ## 已知限制
247
+
248
+ - 只对齐 UI,不对齐功能/数据流
249
+ - 寻路依赖多模态模型对页面文本的识别,小字体或非标准控件可能识别不准
250
+ - 双端设备的分辨率/密度差异会影响像素到 vp 的换算,流水线已从各自 `meta.json` 读取真实 `density_factor`,但同一个 Figma 规格下仍可能出现 ±1vp 偏差
251
+ - 需要长按保持、拖拽、语音、传感器等物理输入触发的状态,无法全自动采集,需要用户配合手动操作