@buaa_smat/hometrans 0.1.15 → 0.1.17

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 (36) hide show
  1. package/README.md +108 -99
  2. package/agents/build-fixer.md +15 -14
  3. package/agents/code-reviewer.md +3 -3
  4. package/agents/logic-coder.md +1 -1
  5. package/agents/logic-context-builder.md +1 -1
  6. package/agents/self-tester.md +16 -13
  7. package/dist/cli/config-store.js +127 -29
  8. package/dist/cli/config.js +9 -2
  9. package/dist/cli/init.js +431 -248
  10. package/dist/cli/uninstall.js +103 -8
  11. package/dist/context/index.js +19 -2
  12. package/env-requirements.json +19 -23
  13. package/package.json +1 -1
  14. package/resource/choose_editor.png +0 -0
  15. package/resource/set_environment.png +0 -0
  16. package/resource/set_multimodel.png +0 -0
  17. package/resource/set_sdk.png +0 -0
  18. package/skills/hmos-batch-ui-align/SKILL.md +8 -6
  19. package/skills/hmos-convert-pipeline/SKILL.md +7 -7
  20. package/skills/hmos-fix-build-errors/SKILL.md +4 -3
  21. package/skills/hmos-incremental-ui-align/README.md +9 -12
  22. package/skills/hmos-incremental-ui-align/SKILL.md +9 -9
  23. package/skills/hmos-incremental-ui-align/scripts/__pycache__/app_feature_verify.cpython-314.pyc +0 -0
  24. package/skills/hmos-incremental-ui-align/scripts/app_feature_verify.py +128 -29
  25. package/skills/hmos-incremental-ui-align/scripts/navigation-capure.md +37 -37
  26. package/skills/hmos-incremental-ui-align/scripts/page_capture.py +7 -2
  27. package/skills/hmos-integration-test/README.md +3 -3
  28. package/skills/hmos-integration-test/SKILL.md +7 -7
  29. package/skills/hmos-spec-generate/SKILL.md +45 -52
  30. package/tools/test-tools/autotest/README.md +10 -11
  31. package/tools/test-tools/autotest/self_test_runner.py +40 -12
  32. package/resource/common_config.png +0 -0
  33. package/resource/integration_test_config.png +0 -0
  34. package/resource/set_env.png +0 -0
  35. package/resource/ui_align_config.png +0 -0
  36. package/skills/hmos-resources-convert/tools/apktool.bat +0 -85
@@ -1,22 +1,32 @@
1
1
  #!/usr/bin/env python3
2
2
  # -*- coding: utf-8 -*-
3
+ # /// script
4
+ # requires-python = ">=3.12"
5
+ # dependencies = [
6
+ # "openpyxl",
7
+ # "phone-agent",
8
+ # ]
9
+ # [[tool.uv.index]]
10
+ # url = "https://pypi.tuna.tsinghua.edu.cn/simple"
11
+ # ///
3
12
  """
4
13
  应用页面导航工具 —— 使用 PhoneAgent 按指定路径操作设备,导航到目标页面。
5
14
 
6
15
  支持 Android (ADB) 和 HarmonyOS (HDC) 两种设备类型。
7
16
 
8
- 依赖: pip install openpyxl phone-agent
17
+ 依赖: openpyxlphone-agent —— 已在文件顶部 PEP 723 元数据声明,`uv run` 自动安装(无需手动 pip install)
9
18
  运行前需确保: 设备已连接 (HDC/ADB)、模型 API 可用
10
19
 
11
20
  用法:
12
21
  # 自由 prompt 导航
13
- python app_feature_verify.py --device adb --app Gallery --package com.example.gallery --prompt "打开相册,进入设置页面"
22
+ uv run app_feature_verify.py --device adb --app Gallery --package com.example.gallery --prompt "打开相册,进入设置页面"
14
23
 
15
24
  # 按功能路径导航
16
- python app_feature_verify.py --app 简单图库 --package com.simplemobiletools.gallery.pro --task "L1相册详情L2更多L3筛选媒体文件"
25
+ uv run app_feature_verify.py --app 简单图库 --package com.simplemobiletools.gallery.pro --task "L1相册详情L2更多L3筛选媒体文件"
17
26
  """
18
27
 
19
28
  import argparse
29
+ import json
20
30
  import re
21
31
  import sys
22
32
  import os
@@ -267,25 +277,62 @@ def _apply_phone_agent_patches():
267
277
  _swipe._patched = True
268
278
  _hdc.swipe = _swipe
269
279
 
270
- # Patch 2: parse_actionregex fallback when do() contains unescaped quotes
280
+ # Patch 2: _parse_responsestrip <think>/<answer> tags before splitting by do()/finish()
281
+ import phone_agent.model.client as _client
282
+ if not getattr(_client.ModelClient._parse_response, '_patched', False):
283
+ _orig_parse_response = _client.ModelClient._parse_response
284
+
285
+ def _patched_parse_response(self, content: str):
286
+ # Strip <think>...</think> and <answer>...</answer> wrappers first
287
+ cleaned = re.sub(r'<think>.*?</think>\s*', '', content, flags=re.DOTALL)
288
+ m = re.search(r'<answer>\s*(.*?)\s*</answer>', cleaned, re.DOTALL)
289
+ if m:
290
+ cleaned = m.group(1)
291
+ # Also strip trailing </answer> if only opening was consumed by rule 2
292
+ cleaned = cleaned.replace('</answer>', '').strip()
293
+ if cleaned:
294
+ return _orig_parse_response(self, cleaned)
295
+ return _orig_parse_response(self, content)
296
+
297
+ _patched_parse_response._patched = True
298
+ _client.ModelClient._parse_response = _patched_parse_response
299
+
300
+ # Patch 3: parse_action — strip thinking tags + regex fallback for malformed do()
271
301
  import phone_agent.actions.handler as _handler
272
302
  if not getattr(_handler.parse_action, '_patched', False):
273
303
  _orig_parse = _handler.parse_action
304
+ _THINK_RE = re.compile(r'<think>.*?</think>\s*', re.DOTALL)
305
+ _ANSWER_RE = re.compile(r'<answer>\s*(.*?)\s*</answer>', re.DOTALL)
306
+
307
+ def _strip_llm_tags(text: str) -> str:
308
+ text = _THINK_RE.sub('', text)
309
+ m = _ANSWER_RE.search(text)
310
+ if m:
311
+ text = m.group(1)
312
+ return text.strip()
313
+
274
314
  def _parse_action(response):
315
+ cleaned = _strip_llm_tags(response)
275
316
  try:
276
- return _orig_parse(response)
277
- except ValueError as e:
278
- if 'Failed to parse do() action' in str(e):
279
- m = re.search(
280
- r'do\s*\(\s*action\s*=\s*"([^"]*)"\s*,\s*message\s*="(.*)"\s*\)\s*$',
281
- response.strip(), re.DOTALL,
282
- )
283
- if m:
284
- msg = m.group(2)
285
- msg = msg.replace("\\n", "\x0a").replace("\\t", "\x09").replace("\\r", "\x0d")
286
- msg = msg.replace('\\"', '"')
287
- return {"_metadata": "do", "action": m.group(1), "message": msg}
288
- raise
317
+ return _orig_parse(cleaned)
318
+ except ValueError:
319
+ pass
320
+ # regex fallback for do() with unescaped quotes
321
+ for text in (cleaned, response.strip()):
322
+ m = re.search(
323
+ r'do\s*\(\s*action\s*=\s*"([^"]*)"\s*,\s*message\s*="(.*)"\s*\)\s*$',
324
+ text, re.DOTALL,
325
+ )
326
+ if m:
327
+ msg = m.group(2)
328
+ msg = msg.replace("\\n", "\x0a").replace("\\t", "\x09").replace("\\r", "\x0d")
329
+ msg = msg.replace('\\"', '"')
330
+ return {"_metadata": "do", "action": m.group(1), "message": msg}
331
+ # fallback: extract bare do() from anywhere in the text
332
+ m = re.search(r'do\s*\(\s*action\s*=\s*"([^"]*)".*?\)', cleaned)
333
+ if m:
334
+ return _orig_parse(m.group(0))
335
+ return _orig_parse(response)
289
336
  _parse_action._patched = True
290
337
  _handler.parse_action = _parse_action
291
338
 
@@ -374,6 +421,63 @@ def navigate(
374
421
  agent.reset()
375
422
 
376
423
 
424
+ # ---------------------------------------------------------------------------
425
+ # 模型配置加载:UI 对齐与自测共用同一个多模态模型。
426
+ # 优先级:CLI --api-key > HOMETRANS_MODEL_* 环境变量 > ~/.hometrans/config.json
427
+ # 的 autotest.unified_model(由 `ht init` 写入,两端共用)> 老版本 GLM_API_KEY(兼容)。
428
+ # ---------------------------------------------------------------------------
429
+
430
+ def _load_model_config(cli_api_key: str = ""):
431
+ """加载统一多模态模型配置(UI 对齐 + 自测共用)。
432
+
433
+ `ht init` 把模型四要素(api_key / name / base_url / provider)写入
434
+ `autotest.unified_model`,并导出为 HOMETRANS_MODEL_* 环境变量。这里优先读
435
+ 环境变量,缺字段时回落到 config.json,单个字段可被 CLI/环境变量覆盖;
436
+ 都没有时再兼容老版本的 GLM_API_KEY(走智谱 GLM 端点)。
437
+ """
438
+ from phone_agent.model import ModelConfig
439
+
440
+ # 1) 环境变量(ht init 导出)+ CLI 覆盖
441
+ api_key = (cli_api_key or os.environ.get("HOMETRANS_MODEL_API_KEY", "")).strip()
442
+ base_url = os.environ.get("HOMETRANS_MODEL_BASE_URL", "").strip()
443
+ model_name = os.environ.get("HOMETRANS_MODEL_NAME", "").strip()
444
+ temperature = 0.1
445
+
446
+ # 2) 回落 ~/.hometrans/config.json 的 autotest.unified_model(补齐缺失字段)
447
+ config_path = Path.home() / ".hometrans" / "config.json"
448
+ if config_path.exists():
449
+ try:
450
+ cfg = json.loads(config_path.read_text(encoding="utf-8"))
451
+ um = cfg.get("autotest", {}).get("unified_model", {})
452
+ api_key = api_key or str(um.get("api_key", "")).strip()
453
+ base_url = base_url or str(um.get("base_url", "")).strip()
454
+ model_name = model_name or str(um.get("name", "")).strip()
455
+ if um.get("temperature") is not None:
456
+ temperature = um.get("temperature")
457
+ except (json.JSONDecodeError, KeyError):
458
+ pass
459
+
460
+ if api_key:
461
+ return ModelConfig(
462
+ base_url=base_url or "http://localhost:8000/v1",
463
+ api_key=api_key,
464
+ model_name=model_name,
465
+ temperature=temperature,
466
+ )
467
+
468
+ # 3) 向后兼容老版本:仅设置了 GLM_API_KEY 时,回落到智谱 GLM 端点。
469
+ glm_key = os.environ.get("GLM_API_KEY", "").strip()
470
+ if glm_key:
471
+ return ModelConfig(
472
+ base_url="https://open.bigmodel.cn/api/paas/v4",
473
+ api_key=glm_key,
474
+ model_name="autoglm-phone",
475
+ temperature=0.1,
476
+ )
477
+
478
+ return ModelConfig(temperature=temperature)
479
+
480
+
377
481
  # ---------------------------------------------------------------------------
378
482
  # CLI 入口
379
483
  # ---------------------------------------------------------------------------
@@ -395,7 +499,7 @@ def main():
395
499
  parser.add_argument("--limit", type=int, default=None, help="仅导航前 N 条")
396
500
  parser.add_argument("--offset", type=int, default=1, help="从第几条开始(1-based)")
397
501
  parser.add_argument("--sheet", type=int, default=0, help="Excel 工作表索引")
398
- parser.add_argument("--api-key", type=str, default="", help="智谱 API Key")
502
+ parser.add_argument("--api-key", type=str, default="", help="API Key(可选,不传则读取 HOMETRANS_MODEL_* 环境变量 / ~/.hometrans/config.json)")
399
503
  parser.add_argument("--max-steps", type=int, default=30, help="Agent 最大步数")
400
504
  parser.add_argument("--app-hints", type=str, default=None, help="应用上下文提示")
401
505
  parser.add_argument("-q", "--quiet", action="store_true", help="减少输出")
@@ -404,19 +508,14 @@ def main():
404
508
  if not args.prompt and not args.task and not args.feature:
405
509
  parser.error("必须指定 --feature、--task 或 --prompt 之一")
406
510
 
407
- api_key = args.api_key or os.environ.get("GLM_API_KEY", "")
408
- if not api_key and not args.quiet:
409
- print("提示: 未设置 API Key,请通过 --api-key 或环境变量 GLM_API_KEY 配置")
410
- print("继续运行可能因鉴权失败而报错\n")
411
-
412
511
  from phone_agent.model import ModelConfig
413
512
 
414
- model_config = ModelConfig(
415
- base_url="https://open.bigmodel.cn/api/paas/v4",
416
- api_key=api_key,
417
- model_name="autoglm-phone",
418
- temperature=0.1,
419
- )
513
+ model_config = _load_model_config(args.api_key)
514
+ if not model_config.api_key or model_config.api_key == "EMPTY":
515
+ if not args.quiet:
516
+ print("提示: 未找到可用的模型配置。请运行 `ht init` 配置统一模型(UI 对齐 + 自测共用),")
517
+ print("它会写入 ~/.hometrans/config.json 的 autotest.unified_model 并导出 HOMETRANS_MODEL_* 环境变量;")
518
+ print("或通过 --api-key 传入 API Key。\n")
420
519
 
421
520
  device_label = "ADB (Android)" if args.device == "adb" else "HDC (HarmonyOS)"
422
521
  print(f"设备: {device_label}, 应用: {args.app} ({args.package})")
@@ -1,37 +1,37 @@
1
- Create a timestamped output directory under `capture_output_dir`:
2
- ```
3
- task_dir = capture_output_dir / "task_{timestamp}"
4
- android_capture_dir = task_dir / "android_page_{i}_{name}"
5
- hmos_capture_dir = task_dir / "hmos_page_{i}_{name}"
6
- ```
7
- Each paired hmos-android page-pair should have the same `i`
8
-
9
- ## Navigation prompt rules
10
-
11
- Each page's `android_nav_path` / `hmos_nav_path` is used as the `--prompt` for `app_feature_verify.py`.
12
- The `--prompt` mode auto-prepends "打开{app_name}," so do not include "打开{app_name}" in the prompt.
13
-
14
- ## Navigate to Android page and Capture (If navigated Success)
15
- ```bash
16
- PYTHONIOENCODING=utf-8 python ./app_feature_verify.py \
17
- --device adb \
18
- --app "{android.app_name}" \
19
- --package "{android.package}" \
20
- --prompt "{pages[i].android_nav_path}" \
21
- --api-key "{glm_api_key}" \
22
- --max-steps 15
23
- ```
24
-
25
- **Capture Android page** (if navigation succeeded)
26
- ```bash
27
- PYTHONIOENCODING=utf-8 python ./page_capture.py --device adb -o "{android_capture_dir}"
28
- ```
29
-
30
- ## Navigate & Capture HarmonyOS Pages
31
-
32
- Same as Andorid but with `--device hdc` and HarmonyOS config values. Directory pattern:
33
- ```
34
- hmos_capture_dir = task_dir / "hmos_page_{i}_{name}"
35
- ```
36
- For pages missed in harmony OS app, navigation will certainly fail — `hmos_capture_dir` will be empty, which is expected. Skip navigation for these pages and leave the directory empty.
37
-
1
+ Create a timestamped output directory under `capture_output_dir`:
2
+ ```
3
+ task_dir = capture_output_dir / "task_{timestamp}"
4
+ android_capture_dir = task_dir / "android_page_{i}_{name}"
5
+ hmos_capture_dir = task_dir / "hmos_page_{i}_{name}"
6
+ ```
7
+ Each paired hmos-android page-pair should have the same `i`
8
+
9
+ ## Navigation prompt rules
10
+
11
+ Each page's `android_nav_path` / `hmos_nav_path` is used as the `--prompt` for `app_feature_verify.py`.
12
+ The `--prompt` mode auto-prepends "打开{app_name}," so do not include "打开{app_name}" in the prompt.
13
+
14
+ ## Navigate to Android page and Capture (If navigated Success)
15
+ ```bash
16
+ PYTHONIOENCODING=utf-8 uv run ./app_feature_verify.py \
17
+ --device adb \
18
+ --app "{android.app_name}" \
19
+ --package "{android.package}" \
20
+ --prompt "{pages[i].android_nav_path}" \
21
+ \
22
+ --max-steps 15
23
+ ```
24
+
25
+ **Capture Android page** (if navigation succeeded)
26
+ ```bash
27
+ PYTHONIOENCODING=utf-8 uv run ./page_capture.py --device adb -o "{android_capture_dir}"
28
+ ```
29
+
30
+ ## Navigate & Capture HarmonyOS Pages
31
+
32
+ Same as Andorid but with `--device hdc` and HarmonyOS config values. Directory pattern:
33
+ ```
34
+ hmos_capture_dir = task_dir / "hmos_page_{i}_{name}"
35
+ ```
36
+ For pages missed in harmony OS app, navigation will certainly fail — `hmos_capture_dir` will be empty, which is expected. Skip navigation for these pages and leave the directory empty.
37
+
@@ -1,5 +1,10 @@
1
1
  #!/usr/bin/env python3
2
2
  # -*- coding: utf-8 -*-
3
+ # /// script
4
+ # requires-python = ">=3.10"
5
+ # [[tool.uv.index]]
6
+ # url = "https://pypi.tuna.tsinghua.edu.cn/simple"
7
+ # ///
3
8
  """
4
9
  统一页面采集工具 —— 单次采集当前设备页面的 View Tree (XML)、截图,支持滚动采集。
5
10
 
@@ -7,10 +12,10 @@
7
12
 
8
13
  用法:
9
14
  # HarmonyOS
10
- python page_capture.py --device hdc -o ./output
15
+ uv run page_capture.py --device hdc -o ./output
11
16
 
12
17
  # Android
13
- python page_capture.py --device adb -o ./output --serial emulator-5554
18
+ uv run page_capture.py --device adb -o ./output --serial emulator-5554
14
19
  """
15
20
 
16
21
  import argparse
@@ -159,7 +159,7 @@
159
159
  | 达到最大轮数 | `max_rounds_reached` | 已完成配置的最大轮数循环(`max_rounds`,默认 3),仍有失败未解决 |
160
160
  | 编译未产出 HAP(异常) | `no_signed_hap` | build-fixer 未产出签名 HAP,无法继续测试 |
161
161
  | 用例列表为空(异常) | `no_testcases` | `testcases.json` 里 0 条用例,没有可跑的内容。常见原因:解析阶段没识别到 `### Scenario:` 区块 |
162
- | 自测 agent 早退(异常) | `agent_early_exit` | `self-tester` 在跑用例之前就退出了(设备没连、autotest api_key 没配(`~/.hometrans/config.json` 的 autotest 块或 `TEST_API_KEY` 环境变量)、`test-tools/autotest` 目录找不到(`HOMETRANS_TOOL_PATH` 环境变量未配置或失效)、batch 启动失败 / 超时 / 崩溃、`setup=false` 但所需 JSON 缺失等)。报告首行会是 `status: FAIL`,第二行 `reason: <原因>`。**这种情况下不会进入 fix 循环**——这些是环境/前置条件问题,不是应用缺陷 |
162
+ | 自测 agent 早退(异常) | `agent_early_exit` | `self-tester` 在跑用例之前就退出了(设备没连、模型 api_key 没配(`HOMETRANS_MODEL_API_KEY` 环境变量或 `~/.hometrans/config.json` 的 `autotest.unified_model.api_key`)、`test-tools/autotest` 目录找不到(`HOMETRANS_TOOL_PATH` 环境变量未配置或失效)、batch 启动失败 / 超时 / 崩溃、`setup=false` 但所需 JSON 缺失等)。报告首行会是 `status: FAIL`,第二行 `reason: <原因>`。**这种情况下不会进入 fix 循环**——这些是环境/前置条件问题,不是应用缺陷 |
163
163
 
164
164
  ---
165
165
 
@@ -242,7 +242,7 @@ build-fixer 的输出摘要:编译状态、签名类型、迭代次数、修
242
242
  - 操作系统:**Windows** 是主要测试目标;macOS/Linux 上的核心命令(`hdc`、`uv`、POSIX 工具链)也能跑,Skill 里涉及到的复制操作会按可用 shell 自适应
243
243
  - `hdc` 已安装并在 PATH 中
244
244
  - `uv` 已安装(Python 包管理)
245
- - `~/.hometrans/config.json` `autotest` 块已配置真实 api_key。`api_key` 来源:`ht init` 填好 `TEST_API_KEY` 后自动写入该块;若仍是占位符,`self_test_runner.py` 会在跑用例前用 OS 环境变量 `TEST_API_KEY` 自动补全。运行时由脚本读取该块、物化成临时 YAML 交给 `AutoTest.batch`,不再需要 `config.yaml`
245
+ - api_key 已配置(与 UI 对齐共用同一个多模态模型)。统一链路「环境变量 → config.json 询问」:`self_test_runner.py` 跑用例前优先读 OS 环境变量 `HOMETRANS_MODEL_API_KEY`(由 `ht init` 导出),缺失时回落 `~/.hometrans/config.json``autotest.unified_model.api_key`;环境变量存在时会覆盖 config.json 里的对应字段。运行时由脚本读取整个 `autotest` 块、物化成临时 YAML 交给 `AutoTest.batch`,不再需要 `config.yaml`
246
246
  - 鸿蒙真机通过 USB 连接,`hdc list targets` 能看到设备
247
247
  - 签名后的 `.hap` 文件
248
248
 
@@ -262,7 +262,7 @@ A: 检查 USB 连接,跑 `hdc list targets` 看看有没有设备 SN。重新
262
262
 
263
263
  **Q: 报 autotest 配置缺失 或 api_key 没填?**
264
264
 
265
- A: 三选一:① 设置环境变量 `TEST_API_KEY`(`self_test_runner.py` 跑用例前会自动用它填充);② 重新跑 `ht init` 并填入 `TEST_API_KEY`,它会自动写入 `~/.hometrans/config.json` 的 `autotest` 块;③ 手动编辑 `~/.hometrans/config.json`,把 `autotest.unified_model.api_key` 的 `YOUR_API_KEY_HERE` 替换成真实的 API Key。
265
+ A: 按统一链路「环境变量 → config.json → 询问」配置 api_key,三选一:① 设置环境变量 `HOMETRANS_MODEL_API_KEY`(优先级最高,`self_test_runner.py` 跑用例前会用它覆盖 config.json);② 重新跑 `ht init` 配置统一模型,它会写入 `~/.hometrans/config.json` 的 `autotest.unified_model` 并导出 `HOMETRANS_MODEL_*` 环境变量;③ 手动编辑 `~/.hometrans/config.json`,把 `autotest.unified_model.api_key` 的 `YOUR_API_KEY_HERE` 替换成真实的 API Key。
266
266
 
267
267
  **Q: 前置用例失败了要不要修代码?**
268
268
 
@@ -25,14 +25,14 @@ A signed `.hap` file is required (this is a device test — the HAP will be inst
25
25
 
26
26
  ## Step 0 — Environment Variables Check (run first)
27
27
 
28
- This skill (via `self-tester`) reads its settings **directly from the OS environment variables**. Before parsing inputs, verify (read each from the OS environment: `echo "$VAR"` on macOS/Linux; `$env:VAR` in PowerShell on Windows):
28
+ This skill (via `self-tester`) resolves each setting along the standard chain — **OS environment variable first, then `~/.hometrans/config.json`, then ask the user**. Before parsing inputs, verify (read env vars with `echo "$VAR"` on macOS/Linux; `$env:VAR` in PowerShell on Windows):
29
29
 
30
- | Variable | Purpose | Valid when |
31
- |---|---|---|
32
- | `TEST_API_KEY` | LLM API key AutoTest uses to drive test cases | non-empty |
33
- | `HOMETRANS_TOOL_PATH` | HomeTrans tools dir (`<HOMETRANS_TOOL_PATH>/test-tools/autotest`) | path exists on disk; if unset, falls back to `~/.hometrans/tools` |
30
+ | Variable | config.json fallback | Purpose | Valid when |
31
+ |---|---|---|---|
32
+ | `HOMETRANS_MODEL_API_KEY` *(legacy `TEST_API_KEY` still honored as a fallback)* | `autotest.unified_model.api_key` | multimodal LLM key AutoTest uses to drive test cases (shared with UI alignment) | non-empty / not the placeholder |
33
+ | `HOMETRANS_TOOL_PATH` | `env.HOMETRANS_TOOL_PATH` | HomeTrans tools dir (`<HOMETRANS_TOOL_PATH>/test-tools/autotest`) | path exists on disk; if unset everywhere, falls back to `~/.hometrans/tools` |
34
34
 
35
- If `TEST_API_KEY` is missing/empty, **stop and ask the user to provide it** before proceeding (suggest running `ht init` to persist it as a machine environment variable). `HOMETRANS_TOOL_PATH` may be left unset (it defaults to `~/.hometrans/tools`).
35
+ The runner (`self_test_runner.py`) applies this same chain at run time (env `HOMETRANS_MODEL_API_KEY` overrides `autotest.unified_model.api_key`). If **neither** the env var **nor** `autotest.unified_model.api_key` in `~/.hometrans/config.json` provides a real key, **stop and ask the user to provide it** before proceeding (suggest running `ht init` to persist it as a machine environment variable). `HOMETRANS_TOOL_PATH` may be left unset (it defaults to `~/.hometrans/tools`).
36
36
 
37
37
  ## Step 1 — Parse Inputs
38
38
 
@@ -336,7 +336,7 @@ If failures remain (for `max_rounds_reached`), list them briefly (scenario names
336
336
  | `test_case_path` does not exist | Ask user for correct path, do not proceed |
337
337
  | `hap_path` does not exist | Ask user for correct HAP path, do not proceed |
338
338
  | `output_path` parent does not exist | Create it, proceed |
339
- | Agent returns error / times out | Report the error to user. Suggest: check device connection (`hdc list targets`), verify HAP is valid, check the `autotest` block in `~/.hometrans/config.json` (or the `TEST_API_KEY` env var) has a real api_key |
339
+ | Agent returns error / times out | Report the error to user. Suggest: check device connection (`hdc list targets`), verify HAP is valid, check the `HOMETRANS_MODEL_API_KEY` env var or `autotest.unified_model.api_key` in `~/.hometrans/config.json` has a real api_key |
340
340
  | `<output_path>/self-test-report.md` missing after agent completes | Report: "Agent completed but no report was generated. Check agent output at <output_path>." |
341
341
  | `self-test-report.md` exists but format is unrecognizable | Show the file path, report the first 80 lines as context, let user investigate |
342
342
  | Device not found (agent reports "No device") | Remind user: connect device via USB, verify `hdc list targets` shows the device |
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: hmos-spec-generate
3
- description: "Generate atomic-scenario requirement specs (markdown) from raw .txt requirement batches for Android-to-HarmonyOS migration. Reads a single .txt holding multiple REQ blocks (blank-line separated), explores the Android code graph via GitNexus, writes per-REQ trace files first then synthesizes specs from the trace. Triggers: spec generation, generate spec, requirement to spec, atomic scenarios, scenario decomposition, decompose scenarios, req to spec, code-trace-first, batch req txt."
3
+ description: "Generate atomic-scenario requirement specs (markdown) from raw .txt requirement batches for Android-to-HarmonyOS migration. Reads a single .txt holding multiple REQ blocks (blank-line separated), explores the Android code graph via homegraph, writes per-REQ trace files first then synthesizes specs from the trace. Triggers: spec generation, generate spec, requirement to spec, atomic scenarios, scenario decomposition, decompose scenarios, req to spec, code-trace-first, batch req txt."
4
4
  license: MIT
5
5
  allowed-tools:
6
6
  - Read
@@ -9,23 +9,24 @@ allowed-tools:
9
9
  - Bash
10
10
  - Glob
11
11
  - Grep
12
- - Skill
13
12
  - AskUserQuestion
14
13
  - TaskCreate
15
14
  - TaskList
16
15
  - TaskUpdate
17
- - mcp__gitnexus__query
18
- - mcp__gitnexus__context
19
- - mcp__gitnexus__impact
20
- - mcp__gitnexus__cypher
21
- - mcp__gitnexus__list_repos
16
+ - mcp__homegraph__search
17
+ - mcp__homegraph__explore
18
+ - mcp__homegraph__node
19
+ - mcp__homegraph__impact
20
+ - mcp__homegraph__callers
21
+ - mcp__homegraph__callees
22
+ - mcp__homegraph__files
22
23
  ---
23
24
 
24
25
  # Spec Generator Skill
25
26
 
26
27
  Produce atomic-scenario requirement specs from Android source code for Android-to-HarmonyOS migration. The input is a single `.txt` carrying multiple REQ blocks (blank-line separated); each block is processed end-to-end (3.1 → 3.5) before the next block starts.
27
28
 
28
- This skill uses GitNexus as the code-graph backend: the `gitnexus-cli` skill for index management, and `mcp__gitnexus__*` tools for code queries. Spec synthesis MUST go through a trace file written in 3.3 — never synthesize from conversation context.
29
+ This skill uses homegraph as the code-graph backend: the `homegraph` CLI for index management, and `mcp__homegraph__*` tools for code queries. Spec synthesis MUST go through a trace file written in 3.3 — never synthesize from conversation context.
29
30
 
30
31
  ## Expected Input
31
32
 
@@ -82,9 +83,9 @@ Android source layers:
82
83
 
83
84
  ### Replayable fact
84
85
 
85
- **Rule**: Every fact in the trace MUST be **replayable verbatim** from a tool response. `file:line` anchors come from `mcp__gitnexus__context` / `cypher` results or the line-number prefix of a `Read` output. Phrases with counts or ordering ("called N times", "first X then Y", "synchronously then asynchronously", "again") MUST be derivable from precise tool-driven counting and sequencing (e.g. `mcp__gitnexus__cypher` aggregating callers, or repeated `mcp__gitnexus__impact` calls). Delete any fact that cannot be replayed.
86
+ **Rule**: Every fact in the trace MUST be **replayable verbatim** from a tool response. `file:line` anchors come from `mcp__homegraph__explore` / `node` results or the line-number prefix of a `Read` output. Phrases with counts or ordering ("called N times", "first X then Y", "synchronously then asynchronously", "again") MUST be derivable from precise tool-driven counting and sequencing (e.g. `mcp__homegraph__callers` / `callees` enumeration, or repeated `mcp__homegraph__impact` calls). Delete any fact that cannot be replayed.
86
87
 
87
- **Common failure mode**: Hallucinate detail by analogy with common patterns ("synchronous write then asynchronous overwrite", "set X again"); estimate a line number from a `mcp__gitnexus__context` snippet and drift to a neighbouring symbol; make negative assertions from intuition instead of reverse-lookup with `mcp__gitnexus__impact` / `cypher` / `Grep`.
88
+ **Common failure mode**: Hallucinate detail by analogy with common patterns ("synchronous write then asynchronous overwrite", "set X again"); estimate a line number from a `mcp__homegraph__node` / `explore` snippet and drift to a neighbouring symbol; make negative assertions from intuition instead of reverse-lookup with `mcp__homegraph__impact` / `callers` / `Grep`.
88
89
 
89
90
  ---
90
91
 
@@ -125,45 +126,33 @@ Step 0 output: an ordered list of `(title, body, feature)` tuples plus a skip li
125
126
 
126
127
  ### Step 1 — Confirm the Android project is a Git repo
127
128
 
128
- Run `git rev-parse --is-inside-work-tree` inside `android_project_dir`.
129
+ `git rev-parse --is-inside-work-tree` in `android_project_dir`: `true` → Step 2; otherwise STOP — tell the user to `git init` the project first, then re-invoke. Do NOT auto-init / modify the user's repo.
129
130
 
130
- - Output is `true` proceed to Step 2.
131
- - Otherwise → STOP. Tell the user:
131
+ ### Step 2 Ensure homegraph has a fresh index for the project
132
132
 
133
- > `android_project_dir` is not a Git repository. GitNexus indexes by Git identity. Please run:
134
- >
135
- > ```
136
- > cd <android_project_dir>
137
- > git init && git add . && git commit -m "init"
138
- > ```
139
- >
140
- > then re-invoke this skill.
133
+ homegraph exposes two surfaces:
141
134
 
142
- Do NOT auto-`init` / `add` / `commit` the user's project. Do NOT modify `git config`.
135
+ - **Index management** (`init`, `index`, `sync`, `status`) run the `homegraph` CLI via `Bash`.
136
+ - **Code queries** (`search`, `explore`, `node`, `impact`, `callers`, `callees`, `files`) → call `mcp__homegraph__*` tools directly.
143
137
 
144
- ### Step 2 Ensure GitNexus has a fresh index for the project
145
-
146
- GitNexus exposes two surfaces:
147
-
148
- - **Index management** (`status`, `analyze`) → invoke the `gitnexus-cli` skill (CLI wrapper, returns text).
149
- - **Code queries** (`query`, `context`, `impact`, `cypher`, `list_repos`) → call `mcp__gitnexus__*` tools directly.
138
+ homegraph indexes per-project into a local `.homegraph/` directory; there is no central repo registry. Scope every MCP call to the Android project with `projectPath: "<android_project_dir>"` (the skill's working directory is the spec project, NOT the Android project, so the parameter is mandatory — omitting it queries the wrong project).
150
139
 
151
140
  Procedure:
152
141
 
153
- 1. Invoke `gitnexus-cli` skill with `status` against `android_project_dir`.
142
+ 1. Run `homegraph status <android_project_dir>` to check index state.
154
143
  2. Decide based on the output:
155
- - **Not registered** or **stale**invoke `gitnexus-cli` with `analyze`, wait for completion.
144
+ - **Not indexed** (no `.homegraph/`)run `homegraph init -i <android_project_dir>`, wait for completion.
145
+ - **Stale** → run `homegraph sync <android_project_dir>` (incremental; use `homegraph index <android_project_dir> --force` for a full rebuild), wait for completion.
156
146
  - **Fresh** → proceed.
157
- 3. Obtain `<repo-id>` via `mcp__gitnexus__list_repos()`; find the entry whose `path` matches `android_project_dir`; take its `repo` (or equivalent identifier) field.
158
147
 
159
148
  **Hard fail policy** — any of the following stops the skill and surfaces the error to the user; do NOT degrade to `Read` + `Grep`:
160
149
 
161
- - `gitnexus-cli` skill is unavailable → ask the user to run `/setup` first.
162
- - `gitnexus-cli status` or `analyze` returns an error → surface the error verbatim.
163
- - `mcp__gitnexus__list_repos` returns no matching entry ask the user to verify that `analyze` registered the project.
164
- - Any `mcp__gitnexus__*` call in Step 3 fails unexpectedly → surface the error; stop the skill.
150
+ - `homegraph` CLI is not found on PATH → ask the user to install it (`npm install -g homegraph`).
151
+ - `homegraph status`, `init`, `sync`, or `index` returns an error → surface the error verbatim.
152
+ - Any `mcp__homegraph__*` call reports the project is not indexed verify `init -i` ran against `android_project_dir`.
153
+ - Any `mcp__homegraph__*` call in Step 3 fails unexpectedly → surface the error; stop the skill.
165
154
 
166
- > All `mcp__gitnexus__*` calls in Step 3 MUST carry `repo=<repo-id>` (or equivalent scope parameter). Cross-repo results poison the trace.
155
+ > All `mcp__homegraph__*` calls in Step 3 MUST carry `projectPath: "<android_project_dir>"`. A missing or wrong `projectPath` poisons the trace.
167
156
 
168
157
  ### Step 3 — Process each REQ block end-to-end
169
158
 
@@ -179,22 +168,22 @@ Order matters: build the trace from Android source first, then synthesize the sp
179
168
 
180
169
  #### 3.2 Recall — Locate entry points (dual-path union)
181
170
 
182
- Run both paths in parallel within the same turn (emit all mcp calls together); the deduplicated union is the recall result. All mcp calls MUST carry `repo=<repo-id>`.
171
+ Run both paths in parallel within the same turn (emit all mcp calls together); the deduplicated union is the recall result. All mcp calls MUST carry `projectPath: "<android_project_dir>"`.
183
172
 
184
173
  | Path | Approach | Value |
185
174
  |---|---|---|
186
- | **Path 1 · By-concept lookup** | For each entity / UI anchor / intent verb from 3.1, call `mcp__gitnexus__query({ query: "<concept>", repo: "<repo-id>" })` and `mcp__gitnexus__context({ name: "<symbol>", repo: "<repo-id>" })` to find candidate symbols (Activities / Fragments / Composables / ViewModels / Services / Repositories / DAOs). Cross-validate symbol name, file path, and context against `REQ_DESC`. | Direct hits: pages obviously implementing the REQ; data models named after REQ entities. |
187
- | **Path 2 · Reverse-by-usage** | Take the most discriminative keyword (a settings key literal, a persistence column / preference key, a unique business term). `query` → candidate symbol → `mcp__gitnexus__impact({ name: "<symbol>", repo: "<repo-id>" })` returns callers (nested). Walk callers upward until hitting an `Activity` / `Fragment` / `@Composable` host. That host is an entry point. If the walk passes through intermediate dialog / popup / bottom-sheet / inner-composable components that directly reference the target key, record each as a **separate entry point** — these represent distinct user-facing surfaces within the host. | Covers structural blind spots Path 1 misses: deeply nested consumers, pass-through navigation pages, manifest-declared receivers. |
175
+ | **Path 1 · By-concept lookup** | For each entity / UI anchor / intent verb from 3.1, call `mcp__homegraph__search({ query: "<concept>", projectPath: "<android_project_dir>" })` to locate candidate symbols, then `mcp__homegraph__explore({ query: "<symbols / file names>", projectPath: "<android_project_dir>" })` to pull their source + call relations (Activities / Fragments / Composables / ViewModels / Services / Repositories / DAOs). Cross-validate symbol name, file path, and context against `REQ_DESC`. | Direct hits: pages obviously implementing the REQ; data models named after REQ entities. |
176
+ | **Path 2 · Reverse-by-usage** | Take the most discriminative keyword (a settings key literal, a persistence column / preference key, a unique business term). `search` → candidate symbol → `mcp__homegraph__callers({ symbol: "<symbol>", projectPath: "<android_project_dir>" })` (or `mcp__homegraph__impact` for the wider blast radius) returns who reaches it. Walk callers upward until hitting an `Activity` / `Fragment` / `@Composable` host. That host is an entry point. If the walk passes through intermediate dialog / popup / bottom-sheet / inner-composable components that directly reference the target key, record each as a **separate entry point** — these represent distinct user-facing surfaces within the host. | Covers structural blind spots Path 1 misses: deeply nested consumers, pass-through navigation pages, manifest-declared receivers. |
188
177
 
189
- **Behavioral REQ anchor extraction**: when `REQ_DESC` has no explicit literal keyword (e.g. "after track-change X refreshes synchronously"), extract anchors as `(action verb, affected entity)` tuples — e.g. `(track-change, X)` — and feed both into Path 1's `query` and Path 2's seed symbol search.
178
+ **Behavioral REQ anchor extraction**: when `REQ_DESC` has no explicit literal keyword (e.g. "after track-change X refreshes synchronously"), extract anchors as `(action verb, affected entity)` tuples — e.g. `(track-change, X)` — and feed both into Path 1's `search` and Path 2's seed symbol search.
190
179
 
191
180
  **Both paths hit zero** → ask the user via `AskUserQuestion` whether to manually specify an entry point. If declined → write `status: no_recall` in the trace, skip 3.3 / 3.4 / 3.5, flag in Step 4 report. Do NOT fabricate entry points.
192
181
 
193
182
  **Terse REQ handling** — if the REQ body has fewer than ~20 substantive characters, OR contains only topical keywords (e.g. "multi-device", "landscape/portrait") with no concrete action verb / entity:
194
183
 
195
184
  1. **MUST** run two broad enumeration passes (both mandatory, not optional):
196
- - **Pass A · Feature code**: `mcp__gitnexus__cypher` enumeration find all Activity / Fragment / Composable hosts whose `configChanges` manifest attribute is non-default (for orientation / resize cases) or whose layout has a resource qualifier matching the REQ topic (e.g. `-land`, `-sw600dp`, `-night`).
197
- - **Pass B · Settings breadth**: For EACH distinct keyword in the REQ body, run `mcp__gitnexus__query` + `Grep` to enumerate ALL settings / configuration screens that reference that keyword or a semantically related term. Terse REQs frequently span multiple unrelated settings pages; anchoring on the single most obvious page is the primary failure mode. The union of Pass A and Pass B is the recall result.
185
+ - **Pass A · Feature code**: enumerate hosts via `mcp__homegraph__search({ kind: "class" | "component", projectPath })` (Activities / Fragments / Composables), then `Grep` `AndroidManifest.xml` for non-default `configChanges` (orientation / resize cases) and `mcp__homegraph__files({ pattern, projectPath })` / `Grep` over `res/` for layout resource qualifiers matching the REQ topic (e.g. `-land`, `-sw600dp`, `-night`). Manifest attributes and resource qualifiers live outside the code graph, so `Grep` is authoritative there.
186
+ - **Pass B · Settings breadth**: For EACH distinct keyword in the REQ body, run `mcp__homegraph__search` + `Grep` to enumerate ALL settings / configuration screens that reference that keyword or a semantically related term. Terse REQs frequently span multiple unrelated settings pages; anchoring on the single most obvious page is the primary failure mode. The union of Pass A and Pass B is the recall result.
198
187
  2. If ≥3 plausible candidates → list them in the trace's `Status` section as `candidate_hosts: [...]` and proceed normally.
199
188
  3. If <3 candidates → invoke `AskUserQuestion`: show the candidate list, ask which are in-scope for this REQ. Do NOT silently guess.
200
189
  4. Mark in trace `Status` block: `terse_req: true` (this signal feeds 3.4 review).
@@ -210,16 +199,20 @@ Run both paths in parallel within the same turn (emit all mcp calls together); t
210
199
 
211
200
  The trace file is the sole machine-readable contract delivered to 3.5. Exploration and writing form a single unit.
212
201
 
213
- **Exploration checklist — execution order** (all mcp calls carry `repo: "<repo-id>"`):
202
+ **Exploration checklist — execution order** (all mcp calls carry `projectPath: "<android_project_dir>"`):
214
203
 
215
204
  | Step | Action | Tool |
216
205
  |---|---|---|
217
- | 1 | Survey candidate symbols for each anchor from 3.1 | `mcp__gitnexus__query` / `context` |
218
- | 2 | Full call chain (callers / callees, up and down) | `mcp__gitnexus__impact` |
219
- | 3 | Cross-class graph traversal / reverse-lookup state writers / aggregation | `mcp__gitnexus__cypher` |
206
+ | 1 | Survey candidate symbols for each anchor from 3.1 | `mcp__homegraph__search` → `mcp__homegraph__explore` / `node` |
207
+ | 2 | Full call chain (callers / callees, up and down) | `mcp__homegraph__callers` / `callees` / `impact` |
208
+ | 3 | Cross-class traversal / reverse-lookup state writers / aggregation | `mcp__homegraph__impact` (deep reverse closure; raise `depth`), then `Grep` for literal keys |
220
209
  | 4 | Exact literal positioning (settings keys, DAO columns, intent actions, preference `android:key`) | `Grep` |
221
210
  | 5 | Final `file:line` verification / zero-hit counter-evidence | `Read` (small window) / `Grep` |
222
211
 
212
+ > **homegraph has no `cypher`.** Split its two roles by whether you have a seed symbol:
213
+ > - **Reverse-lookup from a known symbol** (who writes / reaches this state, cross-class closure, aggregation) → prefer `mcp__homegraph__impact({ symbol, depth, projectPath })` and raise `depth` for multi-hop. It traverses the full reverse-dependency closure — deeper than `callers` (depth-1). Use `callers` / `callees` only for a single hop.
214
+ > - **Seedless enumeration** (all hosts of a kind, anything matching a pattern / manifest attribute / resource qualifier) → `impact` cannot help (it needs a starting symbol); use `mcp__homegraph__search({ kind })` + `mcp__homegraph__files` + `Grep`.
215
+
223
216
  **Exploration dimensions — information coverage** (orthogonal to the checklist; checklist = order, dimensions = what to capture). Apply each to every recall entry:
224
217
 
225
218
  - **Trigger code** — UI control + event callback (click / long-press / swipe / lifecycle / broadcast receiver) invoking the REQ behavior. Fill Entry's `code` and `resource` layer fields.
@@ -238,8 +231,8 @@ The trace file is the sole machine-readable contract delivered to 3.5. Explorati
238
231
 
239
232
  ## Status
240
233
  status: ok | no_recall | error
241
- repo-id: <repo-id>
242
- backend: gitnexus
234
+ project-path: <android_project_dir>
235
+ backend: homegraph
243
236
 
244
237
  ## Recalled entry points (Path 1 + Path 2 union, deduplicated)
245
238
  - Entry 1: <UI path / page name> — file:line — recalled by: path 1 | path 2 | both
@@ -285,7 +278,7 @@ backend: gitnexus
285
278
  ### Non-consumers (boundary counter-examples with evidence)
286
279
  - claim: <e.g. "App widget does not read this key">
287
280
  closure_layers: [code, resource, manifest]
288
- tools: [Grep "<pattern>", mcp__gitnexus__cypher "<...>", Read "<path>"]
281
+ tools: [Grep "<pattern>", mcp__homegraph__impact "<symbol>", Read "<path>"]
289
282
  zero_hits: <verbatim zero-hit evidence — file globs searched, regex used, hit count>
290
283
 
291
284
  ## Same-source cross-reference (if applicable)
@@ -298,9 +291,9 @@ backend: gitnexus
298
291
 
299
292
  Run the checks below against the trace from 3.3; every check must pass before proceeding to 3.5.
300
293
 
301
- - **No fabrication** — every declared `file:line` actually exists (sample-verify via `mcp__gitnexus__context` or `Read`); every behavioral claim is source-supported; every boundary claim has tool evidence.
294
+ - **No fabrication** — every declared `file:line` actually exists (sample-verify via `mcp__homegraph__node` or `Read`); every behavioral claim is source-supported; every boundary claim has tool evidence.
302
295
  - **No omission** — every detail in `REQ_DESC` has a counterpart in the trace (Entry / behavior / deviation). If REQ mentions a feature code does not implement, record as deviation, not omission.
303
- - **Method-discipline compliance** (Multi-layer claim + Replayable fact) — sample-verify random `file:line` anchors via `mcp__gitnexus__context` / `Read` (≥1 line drift → fail); sample-verify every count / ordering phrase against source (unreplayable → fail); every Entry / Touched-entry has all three layer fields filled (hit or `N/A: <reason>`); every Non-consumer claim has all four structured fields (`claim` / `closure_layers` / `tools` / `zero_hits`).
296
+ - **Method-discipline compliance** (Multi-layer claim + Replayable fact) — sample-verify random `file:line` anchors via `mcp__homegraph__node` / `Read` (≥1 line drift → fail); sample-verify every count / ordering phrase against source (unreplayable → fail); every Entry / Touched-entry has all three layer fields filled (hit or `N/A: <reason>`); every Non-consumer claim has all four structured fields (`claim` / `closure_layers` / `tools` / `zero_hits`).
304
297
  - **Implicit triggers coverage** — the trace's `Implicit triggers` section is present and explicitly filled (≥1 trigger OR `None`). Absence of the section = fail review.
305
298
  - **Trace section presence audit** — every trace MUST contain these H2 sections (with `None` if truly empty; absence = fail): `Status` / `Recalled entry points` / per `Entry` (all 4 fields: `claim` / `layers` / `interaction` / `data_flow`) / `Implicit triggers` / `Core business entities` / `Cross-entry shared declarations` / `Deviations from REQ_DESC` / `Scope / Boundary` / `Same-source cross-reference`.
306
299
 
@@ -328,5 +321,5 @@ Once all REQ blocks are processed (or skipped with warnings), produce a brief re
328
321
 
329
322
  ## Forbidden
330
323
 
331
- - **No nested LLM-agent skill calls** — no `gitnexus-exploring`, no `gitnexus-debugging`, no `gitnexus-refactoring`, no `gitnexus-impact-analysis`. They override this SKILL's instructions and cause role drift. **Exception**: `gitnexus-cli` is a CLI wrapper (not an LLM agent); using it for index management is allowed.
324
+ - **No nested LLM-agent skill calls** — no `homegraph-exploring`, no `homegraph-debugging`, no `homegraph-refactoring`, no `homegraph-impact-analysis`. They override this SKILL's instructions and cause role drift. Index management uses the `homegraph` CLI directly (`Bash`), not a skill.
332
325
  - **No modification of the Android project** — `.gradle`, `AndroidManifest.xml`, source files. This skill is read-only.