Просмотр исходного кода

新增 Legado Debug Tool 项目级 Skill

5151561 3 месяцев назад
Родитель
Сommit
0ca8b8f5f1

+ 151 - 0
.agents/skills/legado-debug-tool/SKILL.md

@@ -0,0 +1,151 @@
+---
+name: legado-debug-tool
+description: Reproduce, diagnose, and regression-test Legado Android bugs on a connected phone or emulator with the project Debug Tool, extending the tool when a new screen, precondition, action, system UI, fault injection, or observation pattern is encountered. Use when the user mentions debug-tool, asks to reproduce or locate an Android bug, provides a crash/UI/back-navigation/theme-save/export report, wants logcat/screenshots/video evidence, or needs a deterministic scenario added under tools/android. Also use before fixing a reported device-only bug whose real navigation stack, settings, lifecycle, system picker, or resource state matters.
+---
+
+# Legado Debug Tool
+
+## Goal
+
+Establish observable evidence before changing product behavior. Reproduce the user's real flow, distinguish tool failures from app failures, identify the root cause, explain the intended repair, then turn the reproduction into a passing regression scenario.
+
+Read `../../../tools/android/README.md` completely before taking debug actions. Treat it and `tools/android/debug.sh list` as the current capability reference; do not copy a stale action list into the investigation.
+
+## Non-negotiable rules
+
+- Do not treat the reporter's diagnosis as fact. Convert it into a hypothesis and force the suspected exception or state when necessary.
+- Do not edit `src/main` business behavior before reproducing the bug or obtaining equivalent deterministic evidence.
+- Read-only code inspection is allowed to find the correct entry flow, observable state, and injection point.
+- Reproduce the real task stack. If a screen is normally opened from a parent, launch the parent and navigate into it; launching a child Activity as the task root makes Back exit the app by design.
+- Prefer semantic UI nodes, lifecycle state, app-private artifacts, hashes, and logs over fixed coordinates or sleep-only assertions.
+- Count a system picker action only after clicking its final confirmation button. Creating a document opens DocumentsUI; it does not write the final file until “保存” is clicked.
+- Keep fixtures, providers, launch helpers, and fault injection in `src/debug` or `tools/android`. Do not add diagnostic shortcuts to release code.
+- Do not classify a failed scenario as an app bug until its preconditions and intended actions actually ran.
+- Treat every new bug pattern as a Debug Tool capability gap to close. Do not finish with a one-off sequence of shell commands when the flow can be represented as a reusable entry, action, fixture, injection mode, assertion, or artifact.
+- Use ad hoc ADB commands only to explore an unknown UI or provider. Before diagnosing or fixing product code, fold the discovered procedure back into `runner.py`, a scenario JSON, tool tests, and `tools/android/README.md`.
+
+## Workflow
+
+### 1. Establish the environment
+
+Run:
+
+```bash
+adb devices
+tools/android/debug.sh list
+```
+
+Use the only ready device automatically; pass `--device` when multiple devices exist. If the debug APK or `src/debug` changed, run once without `--skip-build`. Otherwise reuse the installed APK.
+
+### 2. Translate the report into a scenario
+
+Write down four things before running or editing:
+
+1. Real entry screen and navigation stack.
+2. Preconditions: settings, stored data, resource state, process restart, or system UI.
+3. User actions in order.
+4. Observable outcome that separates pass from failure.
+
+Map these to one entry, domain actions, and assertions. Reuse an existing scenario when it covers the flow. If not, add a small registered handler plus a JSON scenario; do not create a generic coordinate DSL.
+
+Use `preferences` for primitive String/Boolean settings applied before launch. Use a Debug-only fixture or provider for files, databases, content URIs, delayed failures, and other controlled state.
+
+### 2a. Extend the tool for a new scenario
+
+If the current capability list cannot express the report, improve the tool before touching product code:
+
+- Add an entry when a new real launch surface or task stack is required.
+- Add a domain action when a new user operation must be repeatable.
+- Add a fixture, `preferences` mapping, or Debug-only provider when a new precondition must be injected.
+- Add an assertion when the outcome cannot be proven by existing observations.
+- Add an artifact collector when screenshots, logs, video, private files, or exported data are not yet retained.
+- Add a system-UI helper when DocumentsUI, Photo Picker, permission UI, or another package participates in the flow.
+
+Register capabilities through `ENTRIES`, `ACTIONS`, and `ASSERTIONS`; avoid scenario-name conditionals and avoid growing `execute()` for one bug. Add or update Python tests for validation and helper behavior. Document the new scenario and injection mode in `tools/android/README.md`.
+
+The first successful manual reproduction is discovery, not completion. A reusable scenario that another fresh session can run without oral instructions is the completion criterion.
+
+### 3. Run a baseline before fixing
+
+Examples:
+
+```bash
+tools/android/debug.sh run source-edit/predictive-back
+tools/android/debug.sh run theme/export-current-baseline --skip-build
+```
+
+For intermittent behavior, run independent full sessions rather than only repeating an action in one process. Record every session ID. Do not merge tool/setup failures into the app failure rate.
+
+### 4. Inspect raw evidence
+
+Start with `.debug-runs/<session>/summary.json`, then inspect as relevant:
+
+- `session-logcat.txt`: logs for observed app PIDs and session markers.
+- `logcat.txt`: full device log for system providers, DocumentsUI, Photo Picker, and Toast evidence.
+- `crash.txt`: uncaught app crash evidence; zero bytes means no captured fatal exception.
+- `transition.mp4`: navigation, predictive Back, picker, animation, and transient UI evidence.
+- screenshots: stable state before, during, and after actions.
+- `export.zip`: validate with `unzip -t`, inspect `manifest.json`, entries, sizes, and hashes.
+
+For saved themes or other private data, use `adb exec-out run-as <debug-package>` to inspect the actual file and manifest. Do not accept a Toast as proof that persistence succeeded.
+
+### 5. Classify failures correctly
+
+Treat these as tool/environment failures until corrected:
+
+- entry readiness failed or zero intended actions ran;
+- a stale DocumentsUI/Photo Picker task stayed above the app;
+- the automation never found or confirmed the picker filename;
+- the pulled output was read before its size became stable;
+- a deep Activity was launched without its real parent;
+- an assertion checked a transient Toast after it disappeared.
+
+Treat a result as app evidence only when the scenario proves the preconditions, performs the intended actions, and captures the resulting state or exception.
+
+### 6. Test the hypothesis, not the wording
+
+When a report names `openInputStream`, permissions, a race, or another suspected cause:
+
+- establish a valid baseline first;
+- inject the exact failure class or resource lifecycle separately;
+- delay failure until immediately before the target action when previews may read the same resource;
+- log an explicit arm marker and access marker so ordering is provable;
+- compare missing files, invalid/revoked content URIs, restart state, and normal resources independently.
+
+Never imitate only an error string when the underlying provider or lifecycle behavior can be reproduced.
+
+### 7. Explain the repair before editing
+
+Report:
+
+- exact reproduced flow;
+- whether it passed or failed and how often;
+- artifact links;
+- confirmed root cause versus remaining inference;
+- the smallest root-level repair and its tradeoffs.
+
+If the user asks to review the proposed fix first, stop here. Do not apply a speculative patch.
+
+### 8. Fix and convert to regression
+
+After authorization to fix:
+
+- change the ownership or lifecycle that caused the failure, not only the final exception site;
+- preserve cleanup and rollback behavior;
+- change the reproducing scenario from expected failure to expected success when appropriate;
+- keep controlled negative scenarios for failures the app is still expected to report safely;
+- rerun the same real UI flow on the device and validate produced artifacts.
+
+### 9. Verify and clean up
+
+Run the relevant project test plus the tool tests:
+
+```bash
+python3 -m unittest discover -s tools/android/tests -v
+```
+
+Build/install and run the final device scenario without `--skip-build` after product or Debug APK changes. At the end, stop Gradle and Kotlin daemons according to repository rules; confirm no large build process remains.
+
+## Handoff format
+
+Lead with the outcome. Include the reproduction flow, root cause, fix, tests, and clickable absolute links to `summary.json` and `transition.mp4`. Explicitly say whether product code was changed, whether changes are committed, and whether the worktree is clean.

+ 4 - 0
.agents/skills/legado-debug-tool/agents/openai.yaml

@@ -0,0 +1,4 @@
+interface:
+  display_name: "Legado Debug Tool"
+  short_description: "用真机复现、取证并回归验证 Legado Android 缺陷"
+  default_prompt: "Use $legado-debug-tool to reproduce this Legado bug on the connected Android device, capture evidence, and verify the root cause before changing product code."

+ 63 - 12
tools/android/README.md

@@ -3,8 +3,16 @@
 项目级 Android 调试入口。它负责构建、安装、恢复 fixture、执行领域动作,并将结果写入
 `.debug-runs/<session-id>/summary.json`。
 
-当前提供三个端到端场景(`reader/page-turn`、`bookshelf/open-reader-back`、
-`bookshelf/miuix-hidden-statusbar`):
+当前提供 14 个端到端场景,覆盖阅读翻页、书架返回、书源编辑预见性返回,以及主题资源的
+保存、应用、删除和导出。先用 `list` 查看运行器实际注册的能力:
+
+```bash
+tools/android/debug.sh list
+```
+
+## 快速开始
+
+阅读页连续翻页:
 
 ```bash
 tools/android/debug.sh run reader/page-turn \
@@ -13,7 +21,7 @@ tools/android/debug.sh run reader/page-turn \
   --action-repeat 20
 ```
 
-第二个场景验证从书架打开固定书籍,再返回同一书架:
+从书架打开固定书籍,再返回同一书架:
 
 ```bash
 tools/android/debug.sh run bookshelf/open-reader-back --device emulator-5554
@@ -22,6 +30,53 @@ tools/android/debug.sh run bookshelf/open-reader-back --device emulator-5554
 它通过书籍的 accessibility 标签定位 fixture,不依赖固定坐标;阅读页 ready 使用 `ReadView`
 的 `READER_PAGE` logcat 信号(见下),返回后再次确认同一书籍节点可见。
 
+书源编辑预见性返回必须从真实上一级进入,不能把编辑 Activity 单独作为任务栈根启动:
+
+```bash
+tools/android/debug.sh run source-edit/predictive-back
+```
+
+该场景安装固定书源,先启动书源管理页,通过同一行的编辑按钮进入编辑页,再执行边缘返回手势,
+最后确认书源管理 Activity 恢复且 fixture 书源仍可见。
+
+## 内置场景
+
+| 场景 | 验证目标 |
+| --- | --- |
+| `reader/page-turn` | 阅读页 ready、Activity 可见且翻页后正文变化 |
+| `bookshelf/open-reader-back` | 从书架进入阅读页并返回同一书架 |
+| `bookshelf/miuix-hidden-statusbar` | Miuix + 隐藏状态栏条件下的阅读页返回 |
+| `source-edit/predictive-back` | 从书源管理进入编辑页后执行预见性返回 |
+| `theme/save-current-baseline` | 无外部资源时保存当前主题 |
+| `theme/export-current-baseline` | 在系统文件选择器中确认保存并校验导出 ZIP |
+| `theme/select-day-background` | 经系统照片/文件选择器选图并验证图片复制到应用目录 |
+| `theme/save-background-after-restart` | 直接注入有效图片,重启后保存且资源进入主题包 |
+| `theme/export-background-after-restart` | 直接注入有效图片,重启后导出且校验资源哈希 |
+| `theme/save-missing-file` | 注入缺失文件路径,验证保存失败反馈且不崩溃 |
+| `theme/export-missing-file` | 注入缺失文件路径,验证导出失败反馈且不崩溃 |
+| `theme/save-missing-content-uri` | 在点击保存前让受控 URI 失效,证明 `openInputStream` 调用链 |
+| `theme/export-missing-content-uri` | 在确认导出前让受控 URI 失效,证明 `openInputStream` 调用链 |
+| `theme/export-current-after-deleting-applied-theme` | 保存并应用主题、删除主题包后,当前主题仍可完整导出 |
+
+## 主题资源故障注入
+
+主题场景支持顶层字段 `themeAsset`:
+
+| 值 | 注入状态 |
+| --- | --- |
+| `valid-file` | 将固定 PNG 推到 Debug 应用目录,并把绝对路径写入白天背景设置 |
+| `missing-file` | 写入确定不存在的文件路径 |
+| `missing-content-uri` | 写入 Debug 专用 ContentProvider URI;初始可读,在保存/导出前切换为抛出 `FileNotFoundException` |
+
+`missing-content-uri` 使用仅存在于 `src/debug` 的 `DebugThemeAssetProvider`。运行器在实际点击保存按钮,
+或在 DocumentsUI 中点击最终“保存”按钮之前调用 `armFailure`,日志必须依次出现
+`INJECTED_THEME_ASSET_FAILURE_ARMED` 和 `INJECTED_THEME_ASSET_OPEN`。这样能证明异常由目标业务操作
+触发,不会把页面预览图片产生的读取误判成保存/导出调用。
+
+主题导出场景不会在打开 DocumentsUI 后等待人工操作:运行器会填写唯一文件名、点击系统“保存”,
+等待文件大小稳定后拉回 `export.zip`,再校验 ZIP、`manifest.json`、资源条目和 fixture 哈希。失败产生的
+占位 ZIP 不会被标记为有效 artifact。
+
 ## 用参数复现 bug 前置状态
 
 有些 bug 依赖特定设置(主题引擎、阅读器开关等)。场景可以声明 `preferences`,由
@@ -57,12 +112,6 @@ fixture 是在 `am start` 时由 `DebugScenarioActivity` 现装的,所以只
 tools/android/debug.sh run reader/page-turn --skip-build
 ```
 
-查看当前支持的 entry、action、assertion(含参数):
-
-```bash
-tools/android/debug.sh list
-```
-
 退出码:
 
 | 退出码 | 含义 |
@@ -75,8 +124,9 @@ tools/android/debug.sh list
 | 5 | 场景定义错误 |
 
 fixture 位于 `app/src/debug/assets/debug-fixtures/`,由只存在于 debug 包中的
-`DebugScenarioActivity` 导入。当前的领域动作是 `reader` entry 的 `turn_page`,以及 `bookshelf`
-entry 的 `open_fixture_book` / `press_back`;这里刻意不提供坐标级通用 DSL。
+`DebugScenarioActivity` 导入。当前 entry 为 `reader`、`bookshelf`、`source_manage` 和
+`theme_config`。动作保持领域语义,例如 `turn_page`、`open_fixture_source`、
+`save_current_theme`、`apply_saved_theme`、`delete_saved_theme`,这里刻意不提供坐标级通用 DSL。
 
 entry、action、assertion 通过 `runner.py` 里的 `ENTRIES` / `ACTIONS` / `ASSERTIONS` 注册表扩展:
 新增一个 bug 场景 = 注册一个 handler(`@register_action` / `@register_entry`)再写一个场景 JSON,
@@ -84,7 +134,8 @@ entry、action、assertion 通过 `runner.py` 里的 `ENTRIES` / `ACTIONS` / `AS
 对这些观测的纯读取。用 `list` 命令可以随时查到已注册的能力。
 
 `summary.json` 是主要机器输入。`failure.suspectedFrame` 仅在堆栈中成功解析到应用代码帧时出现,
-并明确携带 `confidence`;`crash.txt` 始终是原始证据。
+并明确携带 `confidence`;`crash.txt` 始终是原始证据。启用 `record` 时还会生成
+`transition.mp4`;主题导出成功时会保留经过校验的 `export.zip`。
 
 阅读页的观测走 logcat 而非 uiautomator:`ReadView.upContent` 在 debug 包(`BuildConfig.DEBUG`)
 下每次渲染都打一条 `READER_PAGE <当前页正文>`,运行器 grep `logcat -s LegadoDebug:I` 取最后一条