@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.
- package/README.md +108 -99
- package/agents/build-fixer.md +15 -14
- package/agents/code-reviewer.md +3 -3
- package/agents/logic-coder.md +1 -1
- package/agents/logic-context-builder.md +1 -1
- package/agents/self-tester.md +16 -13
- package/dist/cli/config-store.js +127 -29
- package/dist/cli/config.js +9 -2
- package/dist/cli/init.js +431 -248
- package/dist/cli/uninstall.js +103 -8
- package/dist/context/index.js +19 -2
- package/env-requirements.json +19 -23
- package/package.json +1 -1
- package/resource/choose_editor.png +0 -0
- package/resource/set_environment.png +0 -0
- package/resource/set_multimodel.png +0 -0
- package/resource/set_sdk.png +0 -0
- package/skills/hmos-batch-ui-align/SKILL.md +8 -6
- package/skills/hmos-convert-pipeline/SKILL.md +7 -7
- package/skills/hmos-fix-build-errors/SKILL.md +4 -3
- package/skills/hmos-incremental-ui-align/README.md +9 -12
- package/skills/hmos-incremental-ui-align/SKILL.md +9 -9
- package/skills/hmos-incremental-ui-align/scripts/__pycache__/app_feature_verify.cpython-314.pyc +0 -0
- package/skills/hmos-incremental-ui-align/scripts/app_feature_verify.py +128 -29
- package/skills/hmos-incremental-ui-align/scripts/navigation-capure.md +37 -37
- package/skills/hmos-incremental-ui-align/scripts/page_capture.py +7 -2
- package/skills/hmos-integration-test/README.md +3 -3
- package/skills/hmos-integration-test/SKILL.md +7 -7
- package/skills/hmos-spec-generate/SKILL.md +45 -52
- package/tools/test-tools/autotest/README.md +10 -11
- package/tools/test-tools/autotest/self_test_runner.py +40 -12
- package/resource/common_config.png +0 -0
- package/resource/integration_test_config.png +0 -0
- package/resource/set_env.png +0 -0
- package/resource/ui_align_config.png +0 -0
- 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
|
-
依赖:
|
|
17
|
+
依赖: openpyxl、phone-agent —— 已在文件顶部 PEP 723 元数据声明,`uv run` 自动安装(无需手动 pip install)
|
|
9
18
|
运行前需确保: 设备已连接 (HDC/ADB)、模型 API 可用
|
|
10
19
|
|
|
11
20
|
用法:
|
|
12
21
|
# 自由 prompt 导航
|
|
13
|
-
|
|
22
|
+
uv run app_feature_verify.py --device adb --app Gallery --package com.example.gallery --prompt "打开相册,进入设置页面"
|
|
14
23
|
|
|
15
24
|
# 按功能路径导航
|
|
16
|
-
|
|
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:
|
|
280
|
+
# Patch 2: _parse_response — strip <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(
|
|
277
|
-
except ValueError
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
)
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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="
|
|
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 =
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
15
|
+
uv run page_capture.py --device hdc -o ./output
|
|
11
16
|
|
|
12
17
|
# Android
|
|
13
|
-
|
|
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`
|
|
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
|
-
-
|
|
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:
|
|
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`)
|
|
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
|
|
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 `
|
|
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`
|
|
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
|
|
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
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
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
|
|
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 `
|
|
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 `
|
|
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
|
-
|
|
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
|
-
|
|
131
|
-
- Otherwise → STOP. Tell the user:
|
|
131
|
+
### Step 2 — Ensure homegraph has a fresh index for the project
|
|
132
132
|
|
|
133
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
142
|
+
1. Run `homegraph status <android_project_dir>` to check index state.
|
|
154
143
|
2. Decide based on the output:
|
|
155
|
-
- **Not
|
|
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
|
-
- `
|
|
162
|
-
- `
|
|
163
|
-
- `
|
|
164
|
-
- Any `
|
|
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 `
|
|
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 `
|
|
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 `
|
|
187
|
-
| **Path 2 · Reverse-by-usage** | Take the most discriminative keyword (a settings key literal, a persistence column / preference key, a unique business term). `
|
|
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 `
|
|
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**: `
|
|
197
|
-
- **Pass B · Settings breadth**: For EACH distinct keyword in the REQ body, run `
|
|
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 `
|
|
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 | `
|
|
218
|
-
| 2 | Full call chain (callers / callees, up and down) | `
|
|
219
|
-
| 3 | Cross-class
|
|
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
|
-
|
|
242
|
-
backend:
|
|
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>",
|
|
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 `
|
|
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 `
|
|
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 `
|
|
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.
|