Imported from xyu20071224-design/reader (
AGENTS.md). Install upstream withnpx skills add xyu20071224-design/reader. Copyright stays with the author.
AGENTS.md — LinguaReader / 语境阅读
面向中文母语学习者的英文阅读器。核心闭环:导入图书 → 点击语境查词 → 收藏例句 → 间隔复习 → CSV 导出,外加多引擎「听书」。离线优先:默认不联网,所有联网能力都是可选增强且必须有离线降级路径。
⚠️ 三个最容易踩的前提
- Gradle 根在
src/,不是仓库根。 所有 gradle 命令都在src/下执行(仓库根没有gradlew);toolchain/build.sh/build.ps1已自己cd过去。 - 开发机已迁到 Linux(CachyOS,2026-09-08),Windows 旧机仍在。 Linux 一律走
./toolchain/build.sh <task>(自带 JDK/SDK/GRADLE_USER_HOME,别裸调gradlew);Windows 旧机走.\toolchain\build.ps1 <task>或.\gradlew.bat。cd不跨命令保留,用工具的workdir参数。 readest-src/、Readest/不是本项目代码。 它们是另一个开源阅读器 Readest 的源码与安装目录,仅作参考(已在.gitignore)。改动本项目时绝不要动它们,也不要把它们的结论当作本项目事实。
常用命令
# Linux(当前开发机;脚本自己切到 src/,无需 cd)
./toolchain/build.sh assembleDebug # 构建调试 APK
./toolchain/build.sh installDebug # 安装到已连接设备/模拟器
./toolchain/build.sh testDebugUnitTest # :app JVM 单元测试(Robolectric,快,改逻辑必跑)
./toolchain/build.sh testDebugUnitTest :shared:test # CI 同款全量单测(:shared 是纯 JVM 库)
./toolchain/build.sh connectedDebugAndroidTest # 仪器测试(需设备)
# 真机上已装正式调试包时,装一个并存的验证包(applicationId 加 .verify 后缀)
./toolchain/build.sh assembleDebug -PverifyBuild
source toolchain/env.sh # 人手调 adb / apksigner / aapt2 前先 source
# Windows 旧机(工作目录:C:\Users\nagisa\work\reader\src)
.\gradlew.bat assembleDebug # 构建调试 APK
.\gradlew.bat installDebug # 安装到已连接设备/模拟器
.\gradlew.bat testDebugUnitTest # JVM 单元测试(Robolectric,快,改逻辑必跑)
.\gradlew.bat connectedDebugAndroidTest # 仪器测试(需设备)
# 真机上已装正式调试包时,装一个并存的验证包(applicationId 加 .verify 后缀)
.\gradlew.bat assembleDebug -PverifyBuild
⚠️ 发 Release / 给用户装的 APK 必须走 toolchain/build.sh(Linux)或 toolchain/build.ps1(Windows)构建(或等价地把 HOME/-Duser.home 重定向到 toolchain/guser-linux / toolchain/guser)。debug 包默认用 user.home 下的 debug.keystore 签名:走脚本签的是工作区内的密钥库,绕过它直接 gradlew 会签成真实用户密钥库——两个签名的包互相拒绝覆盖安装(2026-09-06 v1.6.2 首次发版就踩了:Release 资产传了错误签名的 APK,用户装不上)。
两台机器的 debug.keystore 必须是同一份:2026-09-08 迁到 Linux 时 AGP 自动生成了新密钥(4F:10:73…),与手机上已装包(FF:9D…83:6F)不同,覆盖安装必被拒;已把 Windows 侧 toolchain/guser/.android/debug.keystore 拷到 toolchain/guser-linux/.android/debug.keystore 并复核指纹一致。别删也别重新生成这两份密钥库。
自建 TTS 服务端见 .agents/memory/tts-server-stack.md。
构建事实(src/app/build.gradle.kts、src/build.gradle.kts)
| 项 | 值 |
|---|---|
| applicationId / namespace | com.linguareader.app |
| compileSdk / targetSdk / minSdk | 35 / 35 / 23 |
| versionCode / versionName | 20 / 1.10.1 |
| JDK / jvmTarget | 17 |
| AGP / Kotlin / Gradle | 8.9.1 / 2.1.10 / 8.11.1 |
| Compose BOM | 2025.05.01 |
关键依赖:jsoup(HTML 解析)、pdfbox-android(PDF 文字层)、androidx.webkit。
- 仓库配置阿里云镜像优先(
src/settings.gradle.kts),因为上游仓库在部分网络下 403/reset。加依赖时别把镜像顺序改掉。 src/gradle.properties开了android.overridePathCheck=true(路径含中文时 AGP 会报错)。- 仓库搬过家(三次):
C:\工作文件夹\reader→C:\work\reader→(2026-09-03)D:\reader→(当前)C:\Users\nagisa\work\reader。搬迁已核对:工作树与 HEAD 零差异、单测在新位置全绿;src/local.properties与.agents/记忆里的路径已同步订正,历史快照里的旧路径只出现在 git 历史里。tts-voice-studio/studio.py与scripts/cut_first_3s.py曾硬编码最旧中文路径、直接跑会失败,已于 2026-09-04 改为当前路径(详见.agents/memory/local-tools-and-assets.md)。上面那条overridePathCheck也是旧中文路径留下的。 - Linux 工具链(2026-09-08 落地;全部在
toolchain/内、已 gitignore):jdk-linux(Temurin 17,系统没装 java)、android-sdk-linux(cmdline-tools + platform-tools +platforms;android-35+build-tools;35.0.0)、gradle-home-linux(含init.d/robolectric-offline.gradle,把 Robolectric 的 android-all jar 与 tmpdir 指进工作区)、guser-linux(HOME/ANDROID_USER_HOME;adb key 与 debug.keystore 在此)。src/local.properties的sdk.dir指向toolchain/android-sdk-linux。沙箱把真实$HOME挂成只读,所以这些重定向不是可选优化。重建:toolchain/setup-linux.sh(JDK + cmdline-tools)→toolchain/setup-sdk.sh(SDK 包)。 - 换行符:仓库里存的是 LF。2026-09-08 从 Windows 拷来时整树变 CRLF(273 个文件在 git 里全成「已修改」),已按 index 还原;
gradlew的 index 模式是 100644,新克隆到 Linux 后要么chmod +x src/gradlew,要么走build.sh(它自己会补)。
目录布局
| 路径 | 内容 |
|---|---|
src/ |
Android 主工程的 Gradle 根(settings.gradle.kts / gradlew) |
src/app/src/main/java/com/linguareader/app/ |
应用代码,包名根 |
.../app/ |
顶层 Compose 屏幕与外壳:MainActivity、AppViewModel、ReaderScreen、BookshelfScreen、VocabularyScreen、ReviewUi、ListeningBar、ListeningSettingsSheet、MultiVoiceSettings、AiDrawerSheet、AppSnackbar、ThemeColors |
.../app/reader/ |
WebView 阅读渲染:ReaderScreen 的引擎侧(ReaderScripts 注入 JS、EpubPage、ReaderController) |
.../app/data/ |
导入器(EPUB/TXT/FB2/PDF)、词典、语境分析、书库、生词本、复习与提醒 |
.../app/tts/ |
听书全部实现(26 个文件):播放状态机、合成器、3 类引擎后端(系统 / 自建 OpenAI 兼容 / MiMo;Piper/Azure/火山已于 2026-08-29 移除)、多角色音色 |
.../app/ai/ |
可选联网 AI:语境档案、整句翻译、术语表、说话人 LLM 标注 |
.../app/translation/ |
中文译本对照(F-128,纯离线):三级 DP 对齐、句/段/词级查询、对齐档案读写 |
.../app/packs/ |
资源包系统(词典包 / 预生成音频包 / 音色包):安装、登记表、占用统计。契约在 src/shared/.../packs/;计划权威 方案-资源包系统.md |
src/shared/src/main/java/com/linguareader/shared/ |
纯 Kotlin/JVM 共享层(禁 android.*):查词逻辑、导入器、断句、对齐、packs/(manifest 解析/校验/路径解析/SafeZip 护栏)、tts/TtsCacheKey。桌面迁移与资源包契约都放这里 |
src/app/src/test/ |
JVM 单测(49 个文件,Robolectric;tts / ai / data / translation / 外壳) |
src/app/src/androidTest/ |
仪器测试(13 个文件) |
src/app/src/main/assets/ |
dictionary/ecdict.sqlite(离线词典) |
(运行时)filesDir/packs/ |
资源包安装目录:<type>/<packId>/<version>/ + registry.json;不进 git、不按书清理,占用计入存储页 |
src/app/src/main/res/values{,-en}/strings.xml |
中文(默认)+ 英文文案,两侧各 655 个 string + 12 个 plurals(key 集合必须完全一致,有测试守着) |
tts-server/ |
自建 OpenAI 兼容 TTS 服务端(Python)+ IndexTTS 克隆音色 + frp 内网穿透配置 |
tts-voice-studio/ |
独立 MiMo 试听台(Python 标准库 + 单页 HTML,端口 8002);本地模型后端已于 2026-09-08 移除 |
tools/dsh-voice-console/ |
DSH Web GUI 持久化插件(MiMo 音色控制台):源码在此,链接进 ~/.dsh/profiles/web;见其 README |
bug收集/ |
缺陷文档库(BUG-001 |
.github/workflows/ci.yml |
GitHub Actions 单测 CI(push/PR 自动跑 testDebugUnitTest) |
scripts/、src/scripts/ |
克隆音色制作、音频对比、词典构建、示例 EPUB 生成 |
artifacts/、验证截图/ |
本地验证产物(APK / logcat / 截图),artifacts/ 被 gitignore |
项目约定
- 提交信息:
feat:/fix:/refactor:/test:/chore:+ 中文描述(照现有 git log 的风格)。 - 文案资源化进行中:新增用户可见文案走
strings.xml(zh 默认 +values-en,两边同时加,key 用模块_用途形式如notice_book_imported)。老代码里仍有硬编码字符串,改到哪块顺手迁哪块。 - 用户反馈走全局 Snackbar(
AppSnackbar.kt)。任何「保存/导入/删除成功或失败」都要给反馈——历史上就吃过「AI 保存无反馈」的亏。 - 纯逻辑要能单测:状态机/算法抽成不依赖 Android 的类(先例:
tts/TtsPlaybackEngine.kt被专门抽出来做纯 Kotlin 状态机)。新增算法先写 JVM 单测。 - 隐私边界是产品承诺:新增任何出网调用,必须(a)默认关闭、(b)由用户显式开关控制、(c)失败/未配置时有离线降级。注意实际实现是「AI 总开关
powerEnabled默认 true,但各子开关默认 false 且无 Key」,所以出厂状态零出网 —— 别误以为总开关本身是那道闸。详见.agents/memory/ai-context-translation.md。 - 明文 HTTP 全局放行(
res/xml/network_security_config.xml),因为自建 TTS 服务器常在局域网跑 HTTP。这是有意决定,注释写在文件里。 - 不要提交大文件:APK、logcat、
.onnx模型、参考音频、ffmpeg 都在.gitignore里,保持这样。 - 密钥不入库、不入文档:API Key 由用户在应用内填写。任何文档/记忆文件只写字段名。
Git / GitHub 工作流
远端 origin = github.com/xyu20071224-design/reader,main 是唯一权威线。分布式格局一句话:本地随便折腾(试验分支、worktree、reset 都行),但任何不想丢的工作必须落在 main 或已 push 的分支上——git status 干净且 git log origin/main..main 为空,才可安心关机。
- 合并即推送:feature 分支合并回
main后立刻git push origin main。曾发生过本地 main 领先远端二十多个提交一周没推的情况,等于没有备份。 - feature 分支也要推:
git push -u origin feat/<主题>。单人项目推分支不是为了评审,是异地备份 + 在网页上翻 diff。 - 动手前先
git fetch;push 被拒说明远端分叉,停下来查清再动。曾发生过两条线平行开发同一批功能(译本对照、TTS 修复、tts-server),main 真正分叉、四十多个文件两边都改的事故——多机/多会话并行开发时尤其危险。绝不 force-push main,确需覆盖时必须先把远端现状备份成分支;main已开 GitHub 分支保护(禁 force-push、禁删除),应急覆盖前需先用仓库所有者令牌经 API 临时解除。 - CI(GitHub Actions):
.github/workflows/ci.yml在 push(main / feat / ci / legacy 分支)与所有 PR 上自动跑testDebugUnitTest(Gradle 根在src/,workflow 已设 working-directory)。红了先修再合并;远端状态以 Actions 页为准。 - 同一时间尽量只开一个会话操作本仓库。2026-08-21 与 08-23 两次并行会话撞车:strings.xml 被 concurrent 改动丢 14 个 key、CI 搭建被另一会话抢先完成。确需并行:每个会话开工前
git fetch,分工不重叠文件。 - legacy 备份分支:
legacy/remote-main-20260820(本地+远端长期保留)封存另一账号 2026-08-19/20 的平行开发——:core模块化、ui/ 包重组、facade 层、TermLexiconLearner/TranslationMemorySearch 双实现。其缺陷修复已于 2026-08-23 审阅移植完毕(见bug收集/与 VALIDATION.md 当日条目);剩余是架构方向决策,未表决前不要从该分支合并代码。 - 分支命名统一
feat/<主题>(与提交前缀一致,不混用feature/);合并后即删:git branch -d <name>,推过远端的加git push origin --delete <name>。 - 提交身份保持统一:
git config user.name / user.email全仓库一致。历史上出现过两个身份各推一条线,加剧了分叉排查难度。 - 同机并行用
git worktree add,试验目录删掉后记得git worktree prune清残留。 - 版本发布:tag 用
v<x.y.z>(与versionName一致)并git push origin v<x.y.z>;正式版可在 GitHub 建 Release 附 APK——Release 资产不进 git 历史,不受大文件规则约束。 - 大文件红线对远端同样生效:截图、logcat、APK、模型、测试电子书不入库;远端历史上混入过整目录工件,清理时分批 commit,别再犯。
验证纪律
- 跑测试先看
.agents/memory/build-test-verify.md顶部「跑测试前必读」铁律:Linux 走./toolchain/build.sh(内部exec,安全);Windows 别用build.ps1跑测试(Gradle 守护进程持有重定向句柄会假挂,构建其实已完成)。判据以src/*/build/test-results/**/*.xml的tests/failures/errors为准,别依赖 job/作业状态;:shared用:shared:test;模型名默认deepseek-v4-flash(deepseek-chat已废弃)。 - 改纯逻辑 →
testDebugUnitTest必跑(本地或 CI 皆可,CI 在 push 时自动兜底)。 - 改 WebView 渲染、手势、TTS 播放、通知栏媒体控制 → 必须真机或模拟器实测,这些行为单测覆盖不到。历史真机验证设备:PKB110 / Android 16。
- 分页跟随已于 2026-09-01 解禁(M2 第 2 刀),但解禁的前提别丢:当年章末死循环(2026-08-23 真机事故)的成因是「翻页 → 阅读器位置回报 → 引擎被拽回该页首块」这条回路,现在回报路径整条删除(契约反转成「页面跟朗读」),回路不可能闭合。护栏有三道:
followRangeIntoView只在句子不在当前页时翻、300ms 合并、用户接管窗口内不翻;跟随翻页带origin='tts',Kotlin 侧据此不清高亮。谁要把「阅读位置回报给 TTS」加回来,必须先重新设计抑制机制,否则死循环会原样复活。 - PKB110(ColorOS)已知怪癖:通知权限
pm grant命令成功但检查仍 false、应用通知被 importance=NONE 压制——通知相关仪器测试 assumeTrue 跳过属预期,不是回归。 - 验证结论写进
VALIDATION.md,截图放验证截图/。 - sideload 同一
versionCode覆盖安装有坑,需要并存包时用-PverifyBuild。 - 单条命令超过 1 分钟,立刻自查是否卡住:先看进程/连接还在不在、输出是否还在增长、有没有报错、是否卡在等输入/等网络/等锁;确认是卡住就终止并说明,别让它干等到超时。
- 判据看原始输出,不看退出码:构建/测试任务可能返回 0 但结果红、或非 0 但实际已完成——一律以测试结果 XML 与命令原始 stdout 为准;不许为了让命令「看起来成功」而吞异常、加空判断或跳过断言。
- 结果只认实际执行层:真机现象以设备侧输出为准(进程 pid、logcat、
uiautomator dump、run-as读盘),不许用模拟器、桌面端或单测结果顶替;设备没连上就如实记「未验证」,不许凭空补结论。 - 失败项先复现留证,再最小定位:一次只改一处,改完重跑该测试点及其直接相关回归点;无法定位的写明已排查范围与下一步,不许反复盲改或顺手改无关代码。
- 断言改动的唯一依据是现行实现或既定设计(代码位置、方案编号、项目记忆/
VALIDATION.md);找不到依据时先停下问,不许直接改期望值,也不许改实现去迁就旧断言。 - 注意:
connectedDebugAndroidTest收尾会自动卸载 verify 包(2026-09-12 实测)——任务结束即移除com.linguareader.app.verify与.verify.test。因此事后手动adb uninstall com.linguareader.app.verify必然返回非 0(Failure [DELETE_FAILED_INTERNAL_ERROR]),那不是故障、也不是没卸干净,别据此误判;要确认是否还在,用pm list packages/pm path/run-as,别用卸载命令的退出码。需要留设备现场时,趁测试任务未收尾自行取证据。
Agent 工作区
项目记忆与规则在 .agents/:
.agents/memory/MEMORY.md— 索引,先看这里,再按主题深入 18 个记忆文件(含跨模块的architecture-map.md)。.agents/rules/— 工作规则:memory-maintenance.md(何时、怎么把新知识写回记忆库)、code-and-verification.md(实现范围、平台约束、文案、测试与验证矩阵、联网硬约束、提交规范)。
动手前先读 MEMORY.md;发现记忆与代码不符,以代码为准并顺手修正记忆文件。
