commit fdb20b8ae5a9d0fa47da5cedeaefcd959727bbb4 Author: QiuSW <105186638@qq.com> Date: Tue Aug 4 17:09:52 2026 +0800 docs: add Android app implementation harness diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..9577c03 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,86 @@ +# Brainwave 项目文档 + +> 文档状态:方案基线 +> 最后核验:2026-08-04 +> 当前阶段:Android 工程尚未初始化,需求与工程约束已建立 + +本目录是 Brainwave 的项目知识事实源。产品决策、领域算法、架构边界、验收标准和已知失败模式必须写入仓库;聊天记录、口头约定和临时提示不构成项目规范。 + +## 项目一句话 + +Brainwave 是一个以《易经》三枚铜币法为文化背景的 Android 个人反思工具:用户亲手投掷并录入六次结果,应用在本地确定本卦、之卦和动爻;用户主动点击「解」后,本地内容或 AI 才把既有结果翻译成现代语言,并收束到一项低风险、可撤销的现实行动。 + +## 文档地图 + +| 文档 | 何时阅读 | 权威内容 | +|---|---|---| +| [原始需求](原始需求.txt) | 核对产品最初意图时 | 用户提供的原始需求,不在此文件中扩写 | +| [产品规格](product-spec.md) | 判断功能范围和验收结果时 | 目标、非目标、需求编号、MVP 边界 | +| [领域规则](domain-rules.md) | 修改投币、六爻、卦象映射时 | 唯一允许的起卦算法与不变量 | +| [UX 与东方视觉](ux-design.md) | 修改页面、文案、动画和主题时 | 用户流程、文化表达、无障碍标准 | +| [系统架构](architecture.md) | 新增包、依赖、数据源或网络能力时 | 分层、依赖方向、运行时数据流 | +| [数据与内容](data-content.md) | 修改卦库、历史记录或内容来源时 | 数据契约、授权、隐私和迁移规则 | +| [AI 解释与安全](ai-safety.md) | 修改提示词、模型调用或解释结果时 | AI 调用门、输入输出契约和安全边界 | +| [质量门禁](quality-gates.md) | 实现、评审、发布前 | 自动化验证、需求追踪和完成定义 | +| [实施计划](implementation-plan.md) | 领取任务或判断下一步时 | 阶段、依赖、交付物和退出条件 | +| [决策记录](decisions.md) | 遇到架构分歧或未决问题时 | 已接受决定、默认假设和 TBD | +| [代理工作手册](agent-playbook.md) | 任何编码代理开始工作前 | 检索、修改、验证和交付流程 | +| [失败记忆](failure-memory.md) | 排障、复盘或添加防回归规则时 | 已知风险、症状、护栏和验证方式 | + +## 推荐阅读路径 + +- 实现起卦:本页 → [领域规则](domain-rules.md) → [系统架构](architecture.md) → [质量门禁](quality-gates.md) +- 实现界面:本页 → [产品规格](product-spec.md) → [UX 与东方视觉](ux-design.md) → [系统架构](architecture.md) +- 接入 AI:本页 → [AI 解释与安全](ai-safety.md) → [数据与内容](data-content.md) → [质量门禁](quality-gates.md) +- 修复缺陷:本页 → [失败记忆](failure-memory.md) → 对应领域文档 → [质量门禁](quality-gates.md) +- 引入依赖或调整结构:本页 → [系统架构](architecture.md) → [决策记录](decisions.md) + +## 规范优先级 + +发生冲突时按以下顺序处理: + +1. 用户最新明确决定。 +2. [产品规格](product-spec.md)中的验收标准与非目标。 +3. [领域规则](domain-rules.md)和[AI 解释与安全](ai-safety.md)中的不变量。 +4. [系统架构](architecture.md)中的依赖约束。 +5. [UX 与东方视觉](ux-design.md)及其他实施建议。 + +不能自行消解的冲突必须记入[决策记录](decisions.md),并在继续实现前请求产品决定。 + +## Harness Engineering 原则 + +本项目采用以下仓库约束: + +- **仓库是事实源**:影响实现的信息必须进入版本化文档、代码、测试或脚本。 +- **入口是地图**:本页只负责导航,细节放在专题文档,避免单个巨型说明吞噬上下文。 +- **边界优先**:用明确的领域类型、接口、依赖方向和测试表达不变量。 +- **验证闭环**:每个需求必须能映射到自动化测试或明确的人工验收步骤。 +- **失败可积累**:重复错误进入[失败记忆](failure-memory.md),随后转化为测试、静态检查或更清楚的契约。 +- **文档随代码演进**:改变行为的提交必须同步更新对应文档;不能让实现与事实源分叉。 + +这些原则来自 Harness Engineering 的核心实践:让代理可读取仓库知识、机械执行架构约束,并通过反馈循环验证工作,而不是依赖一次性提示。参考:[OpenAI Harness Engineering](https://openai.com/index/harness-engineering/)。 + +## 当前已确认与未确认 + +已确认: + +- 目标平台为原生 Android。 +- 使用 Kotlin、Jetpack Compose 和单 Activity 架构。 +- 起卦完全在本地完成,AI 不得参与或更改起卦结果。 +- 用户真实投币并录入;MVP 不提供随机起卦按钮。 +- 东方文化表达采用“纸、墨、朱砂、留白”的内容优先风格。 +- AI 仅在用户主动点击「解」之后调用。 + +仍需产品确认的事项记录在[决策记录](decisions.md#未决问题)。任何代理不得把 `TBD` 悄悄变成产品事实。 + +## 文档维护规则 + +每份专题文档必须包含状态或适用范围。发生下列变化时必须更新文档: + +- 用户可见行为改变; +- 领域算法、数据结构或内容版本改变; +- 新增网络、存储或第三方依赖; +- 测试命令、构建方式或发布门禁改变; +- 出现可能再次发生的缺陷。 + +文档链接、需求编号和验证命令将随工程骨架一起接入 CI 检查。当前尚无 Gradle 工程,不能声称任何构建或测试已经通过。 diff --git a/docs/agent-playbook.md b/docs/agent-playbook.md new file mode 100644 index 0000000..337ca63 --- /dev/null +++ b/docs/agent-playbook.md @@ -0,0 +1,153 @@ +# 编码代理工作手册 + +> 状态:Harness 基线 +> 读者:在本仓库中执行分析、实现、评审和修复的编码代理 + +## 1. 开始任务 + +1. 阅读 [文档地图](README.md),再按任务类型读取专题文档。 +2. 检查工作树、已有实现和测试;不要假设方案文档已经被编码。 +3. 找到关联需求编号、架构边界和完成标准。 +4. 把目标拆成可独立验证的小改动,明确不在范围的内容。 +5. 只有必要产品选择无法从仓库确定且错误假设风险较高时,才请求用户决定。 + +根 `AGENTS.md` 创建后应保持为短入口,只包含仓库导航、常用命令和不可违反的不变量,并指向本手册;不要复制全部专题文档。 + +## 2. 事实源与检索顺序 + +1. 当前用户任务和已接受 ADR。 +2. 对应产品需求、领域规则、AI 安全和架构文档。 +3. 自动化测试与 schema。 +4. 代码实现。 +5. 外部官方文档。 + +测试、代码和文档互相矛盾时,不要随意选择最方便的一方。先判断哪一个表达了当前已接受行为;修复时同步其余事实源,并在交付说明中指出冲突。 + +代码发现优先使用仓库配置的知识图谱工具:`search_graph` → `trace_path` → `get_code_snippet` → `query_graph` → `get_architecture`。图工具不足,或搜索字符串、配置和非代码文件时,再使用 `rg`。 + +## 3. 永久不变量 + +任何任务都不得违反: + +- AI、网络、时间、问题文本和随机数不参与起卦。 +- 六爻领域顺序为 bottom-up;6/9 动,7/8 静。 +- 第六轮确认后,AI 和解释层不能修改 `CastResult`。 +- 核心起卦和本地内容离线可用。 +- 开发者密钥不进入 APK、源码、日志和测试夹具。 +- 问题原文和 AI 全文不进入遥测或崩溃日志。 +- 未授权或来源不明的现代译文、字体和图像不能进入发布包。 +- AI 不预测和替用户作高影响决定。 +- 东方文化表达不得牺牲触控、对比度、读屏和字体缩放。 + +若任务显式要求违反其中一项,停止实现并请求产品确认;确认后先更新决策和规范,再改代码。 + +## 4. 实现工作流 + +### 4.1 Inspect + +- 确认当前分支/工作树和用户已有改动。 +- 阅读最小充分上下文,不进行无目的全仓库扫描。 +- 查找已有相似模式、测试 fake、主题令牌和错误类型。 +- 确认依赖版本与官方 API,而不是凭记忆猜测会变化的接口。 + +### 4.2 Plan + +- 用需求编号描述结果,而不是只列文件。 +- 标记数据迁移、隐私、安全、内容授权和 UI 无障碍风险。 +- 优先扩展现有抽象;新抽象必须有两个以上清楚的使用点或隔离重要边界。 + +### 4.3 Implement + +- 做满足验收的最小完整改动。 +- 领域逻辑用封闭类型和纯函数,边界数据先解析再进入领域。 +- UI 使用不可变状态和显式 action;不在 Composable 中访问数据源。 +- 新颜色、字号、间距和文案进入设计/资源令牌。 +- 不顺手重构与任务无关的用户代码,不覆盖脏工作树。 + +### 4.4 Verify + +- 先运行最接近改动的测试,再运行 `verifyLocal` 或等价全量门禁。 +- UI 变更至少检查正常、加载、错误、空、离线和恢复状态。 +- 领域变更运行穷举/属性测试。 +- AI 变更使用 fake server,不依赖实时模型作为唯一证据。 +- 明确记录执行过、未执行和失败的命令;不把预期当结果。 + +### 4.5 Document + +以下变化必须同步文档: + +- 用户可见行为 → [产品规格](product-spec.md)/[UX](ux-design.md) +- 算法或模型 → [领域规则](domain-rules.md) +- 包、依赖或数据流 → [系统架构](architecture.md) +- schema、来源、隐私 → [数据与内容](data-content.md) +- 提示词/模型行为 → [AI 安全](ai-safety.md) +- 命令/门禁 → [质量门禁](quality-gates.md) +- 架构取舍 → [决策记录](decisions.md) +- 可复现失败 → [失败记忆](failure-memory.md) + +## 5. 依赖政策 + +引入依赖前回答: + +1. 现有 AndroidX/Kotlin/项目代码能否清晰完成? +2. 依赖是否维护、兼容当前工具链并有明确许可证? +3. 它增加多少 APK/构建/运行复杂度? +4. 它会不会模糊领域边界或把密钥带入客户端? +5. 测试如何替换或 fake 它? + +新增大型依赖、具体 AI SDK、分析 SDK、字体包或多模块结构必须记录 ADR。 + +## 6. 测试纪律 + +- 修复缺陷先建立可失败的回归测试,再修复。 +- 不删除、跳过或放宽断言来让门禁变绿,除非需求正式改变。 +- 不在测试中使用 `Thread.sleep` 等不稳定等待;使用测试调度器和可控 fake。 +- 不把实时日期、网络和模型输出放入确定性测试。 +- screenshot golden 的更新必须人工确认差异来源。 +- 数据库迁移必须从上一发布 schema 测到当前 schema。 + +## 7. 失败处理 + +命令失败时: + +1. 保存准确命令、错误摘要和环境。 +2. 判断是实现缺陷、环境缺失、测试脆弱还是规范冲突。 +3. 修复根因并重新运行最小失败用例。 +4. 再运行相关全量门禁。 +5. 若相同类别可能复发,更新[失败记忆](failure-memory.md)并增加机械护栏。 + +不得无限重复同一失败命令,也不得用捕获异常、硬编码测试值或关闭检查掩盖失败。 + +## 8. 交付格式 + +最终交付至少说明: + +```markdown +## 结果 +- 完成了什么,对应哪些需求 + +## 关键实现 +- 重要边界或决策 + +## 验证 +- 实际运行的命令和结果 +- 人工验证 + +## 剩余事项 +- 未运行、TBD、外部阻塞或剩余风险 +``` + +如果任务只完成文档或调查,直接说明没有构建/测试,不伪造运行证据。 + +## 9. 定期维护 + +每个里程碑执行一次轻量“熵清理”: + +- 删除无引用文档、死代码和过期 TODO; +- 合并重复 helper 和重复主题令牌; +- 检查实现是否绕过层边界; +- 检查文档链接、状态和验证命令; +- 检查依赖、许可证、内容版本和数据库迁移; +- 将重复评审意见提升为自动化规则。 + +目标不是追求文件数量,而是让下一次代理运行能更快找到真实约束,并从环境得到准确反馈。 diff --git a/docs/ai-safety.md b/docs/ai-safety.md new file mode 100644 index 0000000..d285b9c --- /dev/null +++ b/docs/ai-safety.md @@ -0,0 +1,150 @@ +# AI 解释契约与安全边界 + +> 状态:提供方无关的 MVP 契约;模型供应商与后端仍为 TBD +> 核心原则:AI 解释既有结果,不参与起卦,不替用户决定 + +## 1. 调用门 + +只有同时满足以下条件才允许调用 AI: + +1. 已存在由本地 `CastEngine` 生成并锁定的 `CastResult`。 +2. 本卦、之卦、卦辞和实际动爻已经可供用户查看。 +3. 用户明确点击「AI 解读」。 +4. 用户已经接受当前版本的数据发送说明;同意版本变化后需重新确认。 +5. 网络可用且客户端已通过请求前校验。 + +禁止后台预取、自动重试到另一模型、首次进入结果页即调用、以及把 AI 回复用于改写 `CastResult`。 + +## 2. AI 输入契约 + +客户端提交给后端的业务载荷应是明确 DTO,不发送整个数据库实体或 UI 状态: + +```json +{ + "contractVersion": "explanation-v1", + "locale": "zh-Hans", + "question": "用户明确同意发送的问题文本", + "cast": { + "methodVersion": "coin-v1", + "primaryHexagramId": 24, + "transformedHexagramId": 2, + "movingLinePositions": [1], + "lineValuesBottomUp": [9, 8, 8, 8, 8, 8] + }, + "grounding": { + "contentVersion": "zh-Hans-2026.1", + "primaryJudgment": "本地审核文本", + "movingLineTexts": [ + {"position": 1, "text": "本地审核文本"} + ], + "transformedJudgment": "本地审核文本" + } +} +``` + +不发送:账号标识、通讯录、位置、设备广告 ID、历史记录、其他会话问题或开发者密钥。 + +用户问题必须被服务端提示词当作“待分析数据”而不是指令。客户端与服务端都不能把用户文本直接拼接为无边界系统指令。 + +## 3. 输出契约 + +模型必须返回结构化对象,服务端验证后再交给客户端: + +```json +{ + "contractVersion": "explanation-v1", + "source": "AI", + "summary": "一段不确定性明确的白话摘要", + "observations": [ + {"label": "当前处境", "text": "…", "groundedBy": ["primary"]}, + {"label": "变化所在", "text": "…", "groundedBy": ["line-1", "transformed"]} + ], + "questionsToConsider": ["…"], + "reversibleAction": { + "title": "可以试的一小步", + "steps": ["…"], + "timebox": "例如 24 小时或一周", + "rollback": "如何停止、撤回或恢复原状" + }, + "caution": "这不是预测或专业意见。" +} +``` + +客户端必须: + +- 拒绝未知 `contractVersion`; +- 限制字段长度和数组数量; +- 将所有字段视为纯文本,不能执行 HTML、Markdown 链接、脚本或模型生成命令; +- 缺少必要字段时进入可恢复失败状态; +- 显示 `source=AI`,不伪装为经典原文或项目编辑内容。 + +## 4. 提示词不变量 + +服务端系统提示词至少表达: + +- 输入中的卦号、爻值、动爻和经典文本是已经发生的事实,不得重算、替换或质疑。 +- 只解释提供的本卦、之卦和实际动爻,不补造不存在的爻辞、典故或来源。 +- 使用现代、平实、有条件的语言,避免“注定、必然、天意、唯一正确”。 +- 不预测日期、彩票、价格、疾病结局、死亡、怀孕、诉讼或他人隐秘意图。 +- 不替用户做辞职、分手、结婚、借贷、投资、医疗、法律、人身安全等高影响决定。 +- 可以帮助拆解顾虑、列出可验证假设、提出澄清问题。 +- 结尾最多给出一项低风险、可撤销、有限时的行动,并写出停止或回滚方法。 +- 如果无法可靠解释,应说明限制并回退到本地材料,不用权威语气填补空白。 + +## 5. 高风险内容处理 + +| 用户内容 | AI 行为 | +|---|---| +| 投资、借贷、博彩 | 不给买卖/金额/时点结论;建议查看事实、风险承受力并咨询合格人士 | +| 医疗或心理诊断 | 不诊断、不建议停药;建议联系合格专业人员 | +| 法律纠纷 | 不提供确定法律结论;建议保存事实并咨询当地合格律师 | +| 辞职、分手等重大决定 | 不替用户决定;建议设计小范围验证、冷静期或对话 | +| 自伤、伤人或即时危险 | 停止卦象式解释;以安全为先,鼓励联系当地紧急服务、可信任的人或专业支持 | + +高风险分支的提示和模板需要安全评审与专门测试。地域相关的热线和法律/医疗资源不能由模型临时编造;上线前采用经过维护的资源表或不显示不确定号码。 + +## 6. 本地解释 + +本地解释是可靠降级路径,不是低等级付费占位。它应: + +- 直接读取经审校的本地白话; +- 根据是否有动爻组合固定结构; +- 清楚区分本卦、变化位置与之卦; +- 通过模板提供反思问题,但不根据用户问题生成事实断言; +- 无网络、未同意 AI 或 AI 失败时完整可用。 + +本地模板同样遵守“不预测、不裁决”的产品原则。 + +## 7. 故障与重试 + +| 故障 | 客户端表现 | 是否改变起卦结果 | +|---|---|---| +| 无网络 | 提供本地解读和“稍后重试” | 否 | +| 超时 | 保留结果,允许一次显式重试 | 否 | +| 429/限流 | 告知稍后再试,不循环请求 | 否 | +| 服务端 5xx | 显示通用错误码,保留本地内容 | 否 | +| 非法 JSON/缺字段 | 拒绝渲染,记录脱敏错误码 | 否 | +| 安全拦截 | 使用安全说明或本地材料 | 否 | + +重试必须由用户发起或使用受限、可取消的网络策略;不得因重试得到不同回复而覆盖用户已经保存的解释,除非用户明确选择“重新解读”。 + +## 8. 密钥、日志和服务端 + +- 正式模型密钥只存在于后端密钥管理系统。 +- Android APK 中不包含生产密钥,即使使用混淆、BuildConfig 或 NDK 也不视为安全。 +- 发布构建关闭 HTTP body 日志。 +- 业务日志使用 request ID、合同版本、状态码、延迟桶和模型配置版本;不记录原始问题与回复。 +- 模型、提示词和安全策略都有独立版本,便于复现与回滚。 +- 服务端对输入输出做长度、schema、频率和内容安全检查。 + +Android 端密钥管理参考:[Android 安全清单](https://developer.android.com/privacy-and-security/security-tips)。 + +## 9. AI 验收测试 + +- 同一 `CastResult` 在 AI 请求前后字节级确定字段不变。 +- 没有 `CastResult`、未点击、未同意或结果尚未显示时,网络 mock 收到 0 次调用。 +- 提示注入样例不能让模型重算卦、泄露系统提示或执行用户文本中的指令。 +- 高风险测试集不产生确定的医疗、法律、投资或人生决定。 +- 所有合法输出都有可撤销行动;所有非法输出都被客户端拒绝且可改用本地解释。 +- 网络断开、超时、取消、旋转和进程重建不会重复提交。 +- 日志捕获测试中不出现问题原文、回复全文和密钥样例。 diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..193a34d --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,186 @@ +# Android 系统架构 + +> 状态:已接受的 MVP 架构 +> 技术方向:Kotlin + Jetpack Compose,单模块起步 +> 骨架来源:[Android Architecture Starter Template — base](https://github.com/android/architecture-templates/tree/base) + +## 1. 架构目标 + +- 本地起卦是确定、可测试、无 Android/网络依赖的纯领域逻辑。 +- UI、内容、存储和 AI 可以独立替换,不改变已生成的 `CastResult`。 +- 核心流程离线可用,AI 故障只降级解释能力。 +- 包边界对人和编码代理都清楚,并能通过测试或静态规则检查。 +- MVP 保持单 Gradle 模块,避免过早多模块化;代码仍按领域和层分包。 + +Android 官方建议新应用采用清晰的 UI/数据分层、单向数据流、ViewModel、协程与 Flow;大型复杂业务才按需要增加 domain 层。本项目因为起卦不变量重要,保留轻量 domain 层。[Android 架构指南](https://developer.android.com/topic/architecture) + +## 2. 系统上下文 + +```text +┌──────────────── Android App ────────────────┐ +│ Compose UI │ +│ ↓ intent ↑ immutable state │ +│ ViewModel / state holder │ +│ ↓ │ +│ Domain: CastEngine / explanation use cases │ +│ ↓ interfaces │ +│ Repositories │ +│ ├─ Assets / prepackaged content │ +│ ├─ Room history │ +│ ├─ DataStore settings │ +│ └─ AI gateway ───────────────→ App backend│ +└─────────────────────────────────────────────┘ +``` + +只有 AI 解释需要网络。起念、投币、起卦、卦库读取和本地解释都位于 Android 应用内部。 + +## 3. 建议目录 + +工程初始化后建议采用: + +```text +app/src/main/java// +├── app/ +│ ├── BrainwaveApplication.kt +│ ├── MainActivity.kt +│ └── BrainwaveNavHost.kt +├── core/ +│ ├── model/ # 跨层不可变领域模型 +│ ├── designsystem/ # 主题、字体、间距、卦象 Canvas +│ └── common/ # 极少量通用结果类型/调度器 +├── domain/ +│ ├── casting/ # CastEngine、映射与校验 +│ └── explanation/ # 解释用例与端口 +├── data/ +│ ├── content/ # assets 卦库解析与版本校验 +│ ├── history/ # Room entity、DAO、repository +│ ├── settings/ # DataStore +│ └── ai/ # 网络 DTO、gateway、响应校验 +└── feature/ + ├── onboarding/ + ├── question/ + ├── casting/ + ├── result/ + ├── explanation/ + ├── history/ + └── settings/ +``` + +测试按相同包结构镜像放入 `src/test` 和 `src/androidTest`。 + +## 4. 依赖方向 + +允许: + +```text +feature UI → feature ViewModel → domain use case → repository interface +data implementation → repository interface + core model +app/navigation → feature public route +core/designsystem → Compose/Material + core model(仅绘制需要) +``` + +禁止: + +- `domain` 依赖 Compose、Activity、ViewModel、Room、Retrofit/Ktor 或具体 AI SDK。 +- `CastEngine` 读取系统时间、随机数、网络、数据库或问题文本。 +- Composable 直接访问 DAO、assets、网络客户端或 Hilt entry point。 +- `data/ai` 引用或调用 `CastEngine`。 +- AI DTO 直接成为 UI 状态;必须先校验并映射为领域结果。 +- feature 之间直接引用对方的内部 ViewModel 或 screen 实现。 +- 一个“Utils”包承载无边界的杂项业务逻辑。 + +工程具备代码后,应通过架构测试或静态检查机械执行这些禁止项,不能只依赖评审记忆。 + +## 5. 核心组件职责 + +### `CastEngine` + +纯 Kotlin、无副作用。输入六轮铜币和方法版本,输出不可变 `CastResult`。所有规则来自[领域规则](domain-rules.md)。 + +### `HexagramContentRepository` + +按卦号和 `contentVersion` 返回已校验的本地内容。缺失、重复或版本不兼容属于数据完整性错误,不使用 AI 猜补。 + +### `CastingSessionViewModel` + +维护草稿问题、六轮录入和状态机。通过 `SavedStateHandle` 保存可恢复的进行中状态;只在第六轮确认时调用 `CastEngine`。 + +### `HistoryRepository` + +在用户明确保存后持久化会话。Room 模型不得泄露到 UI;数据库迁移必须有测试。 + +### `ExplanationRepository` + +提供统一接口:本地解释和 AI 解释返回相同的可展示领域结构,并带明确 `source`。AI 实现遵循[AI 解释与安全](ai-safety.md)。 + +## 6. 单向数据流 + +每个 feature 使用不可变 `UiState` 和显式 `UiAction`: + +```text +user action → ViewModel → use case/repository → state update → Compose render +``` + +一次性结果也应建模为状态及消费动作,避免裸 `Channel`/事件在配置变化时丢失。导航由 UI 根据已处理状态触发,并保证重复收集不会重复提交 AI 请求。 + +领域计算成功后 `CastResult` 为只读值。解释状态与起卦状态分开存储: + +```text +ResultUiState( + castResult = immutable, + content = immutable, + explanationState = Idle | Loading | Local | Ai | Failed +) +``` + +## 7. 数据和存储选择 + +- **assets 或预置 Room**:版本化的 64 卦及爻辞。首版如果仅按编号读取,JSON assets 更简单;需要全文检索或内容迁移时再采用预置 Room。 +- **Room**:用户主动保存的会话、解释和行动记录。Android 官方推荐 Room 管理非平凡结构化数据。[Room 文档](https://developer.android.com/training/data-storage/room) +- **DataStore**:方法说明是否已读、主题、AI 同意版本等少量设置;不保存大型历史或完整卦库。[DataStore 文档](https://developer.android.com/topic/libraries/architecture/datastore) +- **SavedStateHandle**:恢复当前未完成流程;不是长期历史数据库。 + +详细契约见[数据与内容](data-content.md)。 + +## 8. 网络与 AI 边界 + +正式发行版本通过自有后端代理模型服务: + +```text +App → HTTPS backend → model provider +``` + +开发者密钥不能放入 APK。网络层只在用户主动选择 AI 解读后工作,设置合理连接/读取超时、取消传播和有限重试。不得后台预取解释,不得在用户继续编辑问题时偷偷重发。 + +如果未来支持用户自带密钥,它是单独的产品模式,需要 Android Keystore、清晰风险说明和独立决策记录,不能与默认发布路径混合。 + +## 9. 安全与隐私 + +- 仅请求联网所需权限;起卦不需要相机、定位、联系人、传感器或存储权限。 +- 所有请求使用 TLS,服务端执行认证、限流和滥用保护。 +- 日志不得包含问题原文、完整提示词、模型回复或 API 密钥。 +- 崩溃与分析事件只记录枚举状态、耗时桶和匿名错误码。 +- 用户删除本地记录后,不保留隐藏副本;服务端数据保留策略需在 AI 上线前单独确认。 +- 任何来自 assets、数据库或网络的数据在边界处解析和校验,失败后进入有恢复路径的错误状态。 + +## 10. 依赖和构建政策 + +- 版本集中在 Gradle Version Catalog。 +- 优先使用 AndroidX、Kotlin 官方组件和维护活跃的小型依赖。 +- 新依赖必须说明用途、维护状态、许可证、体积和是否可由现有能力替代。 +- 禁止直接引入完整“算命 SDK”或无法审计的卦象计算库。 +- Compose 依赖使用 BOM 对齐版本。 +- `compileSdk`/`targetSdk` 使用实现时最新稳定且满足商店要求的版本,不在方案文档硬编码会迅速过期的数值。 +- `minSdk` 默认提案为 26,最终值见[决策记录](decisions.md#未决问题)。 + +## 11. 多模块化触发条件 + +满足以下至少两个条件后再评估从 `base` 迁移为多模块: + +- 两名以上开发者长期并行修改独立 feature; +- 构建时间已经影响反馈循环; +- 有可独立发布/复用的设计系统或领域库; +- 历史、内容浏览、账户等功能使单模块边界难以机械约束; +- 需要独立基准测试或测试应用。 + +迁移前必须新增 ADR;“项目看起来更专业”不是拆模块理由。 diff --git a/docs/data-content.md b/docs/data-content.md new file mode 100644 index 0000000..3d8fea7 --- /dev/null +++ b/docs/data-content.md @@ -0,0 +1,170 @@ +# 数据、内容与隐私契约 + +> 状态:结构已定义,内容来源与授权仍为发布阻塞项 +> 适用范围:卦库 assets、Room、DataStore、导入脚本和内容审核 + +## 1. 数据分类 + +| 数据 | 来源 | 默认位置 | 敏感性 | 是否参与起卦 | +|---|---|---|---|---| +| 三轮币面/六爻结果 | 用户输入/本地计算 | 会话状态,可选 Room | 低至中 | 是 | +| 用户问题 | 用户输入 | 会话状态,可选 Room | 高 | 否 | +| 64 卦与爻辞 | 经审核的内容包 | assets 或预置 Room | 低,关注版权 | 仅用于查表 | +| 本地解释 | 编辑内容 | assets | 低,关注版权 | 否 | +| AI 请求与回复 | 用户主动请求 | 内存,可选 Room | 高 | 否 | +| 偏好与同意版本 | 用户设置 | DataStore | 中 | 否 | + +任何“否”的数据都不能成为 `CastEngine` 输入。 + +## 2. 本地内容包 + +建议使用带清单的版本化 JSON: + +```json +{ + "schemaVersion": 1, + "contentVersion": "zh-Hans-2026.1", + "sources": [ + { + "id": "source-id", + "title": "来源名称", + "edition": "版本说明", + "license": "许可证或公版依据", + "url": "https://example.invalid" + } + ], + "hexagrams": [ + { + "kingWenNumber": 1, + "name": "乾", + "symbol": "䷀", + "lowerTrigram": "QIAN", + "upperTrigram": "QIAN", + "patternBottomUp": ["YANG", "YANG", "YANG", "YANG", "YANG", "YANG"], + "judgmentOriginal": "…", + "judgmentPlain": "…", + "lineTextsBottomUp": ["…", "…", "…", "…", "…", "…"], + "linePlainBottomUp": ["…", "…", "…", "…", "…", "…"], + "specialUsageText": "…", + "sourceRefs": ["source-id"] + } + ] +} +``` + +示例中的省略号不是可发布内容。禁止由 AI 在构建时临时补齐缺失卦辞或爻辞。 + +## 3. 内容完整性门禁 + +内容包进入应用前必须自动验证: + +- `schemaVersion` 为客户端支持值; +- 64 个条目数量准确; +- 文王卦号 1–64 各出现且只出现一次; +- 64 个 `patternBottomUp` 各不重复,且与上下卦一致; +- 每卦恰有六条 bottom-up 爻辞; +- 名称、卦辞、来源引用和许可证字段非空; +- 乾、坤特殊文本是否存在与内容版本声明一致; +- 所有文本为有效 UTF-8,无 HTML/Markdown 脚本或不可见控制字符; +- 生成内容校验摘要,便于安装包与内容版本对应。 + +运行时若内容包校验失败,应用应显示数据完整性错误并禁用结果查表,不能使用错误卦、相邻数组项或 AI 猜测作为降级。 + +## 4. 来源与授权 + +- 古代原文、公版翻译、现代译注和编辑改写必须分栏记录,不能把“古籍公版”错误外推到现代译本。 +- 每段可发布文本必须能追踪到 `sourceRefs` 和授权依据。 +- 现代白话建议由项目自行撰写并审核,或使用明确允许再分发和修改的来源。 +- 字体、纹理、插画和音效同样进入许可证清单。 +- 发布包内提供“内容来源”页面;用户能查看版本和来源。 + +内容负责人确认来源前,64 卦内容任务不可标记完成。 + +## 5. Room 数据模型提案 + +历史记录不是起卦事实源,但应保存可复核的快照: + +```text +CastingSessionEntity +- id: UUID string, primary key +- createdAt: Instant/epoch millis +- methodVersion: string +- contentVersion: string +- coinConvention: enum string +- roundsJson: validated JSON +- lineValues: compact validated representation +- primaryHexagramId: 1..64 +- transformedHexagramId: 1..64 +- movingPositions: validated representation +- questionText: nullable +- questionSaved: boolean + +ExplanationEntity +- id: UUID string, primary key +- sessionId: foreign key +- source: LOCAL | AI +- contractVersion: string +- content: validated structured JSON +- createdAt + +ActionNoteEntity +- sessionId: foreign key +- actionText: nullable +- timeboxText: nullable +- completedState: NOT_SET | PLANNED | DONE | ABANDONED +``` + +约束: + +- 数据库存储原始 18 枚币面和确定结果,读取时可重新计算并进行一致性检查。 +- `questionText` 默认可为空;用户不保存问题时不能偷偷复制到其他表。 +- 删除 session 应级联删除解释和行动记录。 +- enum 使用稳定字符串或显式转换,不依赖 Kotlin ordinal。 +- 每次 schema 迁移必须有 migration test;禁止发布构建使用 destructive migration。 + +## 6. DataStore 范围 + +DataStore 仅保存少量偏好: + +- onboarding 版本是否已读; +- 主题:系统/浅色/深色; +- 减少应用内非必要动画; +- AI 同意文本版本和同意时间; +- 默认解释方式; +- 是否允许保存问题原文。 + +不要把六爻历史、完整内容包或 AI 长文本塞入 DataStore。 + +## 7. 隐私规则 + +- 问题文本被视为敏感用户内容。 +- 本地核心流程不需要网络权限之外的危险权限。 +- 未点击 AI 解读时,问题和卦象不得离开设备。 +- AI 请求前展示准确的数据清单和服务方类别。 +- 日志、分析、崩溃报告、截图测试夹具不得使用真实用户问题。 +- 调试日志使用固定脱敏样例;发布构建关闭网络 body logging。 +- 历史导出、云备份和 Android Auto Backup 策略在实现前必须单独决策。 + +## 8. 数据生命周期 + +```text +草稿问题/未完成投币:SavedStateHandle + 内存 + ↓ 用户完成 +不可变 CastResult:结果页内存 + ↓ 用户明确保存 +Room 历史快照 + +AI 请求:请求期间内存 → 服务端按已披露策略处理 +AI 回复:结果页内存 → 用户保存后可进入 Room +``` + +会话中止后是否保留草稿由产品设置决定;无论如何都不能把草稿作为遥测发送。 + +## 9. 内容变更规则 + +- 修正文案:提升 `contentVersion`,记录来源和审校人。 +- 修改 schema:提升 `schemaVersion`,提供向后兼容或明确迁移。 +- 修改领域算法:提升 `methodVersion`,不得通过内容版本掩盖。 +- 历史页必须按记录中的内容/方法版本解释旧结果,或明确提示当前展示使用了新版内容;不能静默伪装成原始解释。 + +Android 存储选择参考:[数据与文件概览](https://developer.android.com/training/data-storage/)、[Room 预置数据库](https://developer.android.com/training/data-storage/room/prepopulate)。 diff --git a/docs/decisions.md b/docs/decisions.md new file mode 100644 index 0000000..93f50af --- /dev/null +++ b/docs/decisions.md @@ -0,0 +1,125 @@ +# 架构决策与未决问题 + +> 状态:持续维护 +> 规则:已接受决定不得被实现者静默推翻;变更需新增记录并说明迁移影响 + +## 决策状态 + +- `Accepted`:当前实现必须遵守。 +- `Proposed`:有推荐默认值,但仍允许产品确认前调整。 +- `Superseded`:已被后续决策替代,保留历史原因。 +- `Rejected`:明确不采用,避免重复讨论。 + +## ADR-001:使用原生 Android Kotlin + Jetpack Compose + +- 状态:`Accepted` +- 日期:2026-08-04 +- 决定:Android 首版采用 Kotlin、Jetpack Compose、Material 3 和单 Activity。 +- 原因:产品只要求 Android;Compose 支持自定义主题、Canvas 卦象、状态驱动界面、无障碍和自适应布局,且符合官方新应用方向。 +- 后果:不建立 Flutter、React Native 或 WebView 双栈;UI 测试使用 Compose 工具链。 + +## ADR-002:以官方单模块 Architecture Template 为骨架 + +- 状态:`Accepted` +- 日期:2026-08-04 +- 决定:从 `android/architecture-templates` 的 `base` 分支起步,保留单 Gradle module,按层和 feature 分包。 +- 原因:模板包含 Compose、Room、Hilt、ViewModel、Navigation、Flow 和测试基础;MVP 规模不足以抵消多模块复杂度。 +- 后果:满足[系统架构](architecture.md#11-多模块化触发条件)后才能提出多模块 ADR。 + +## ADR-003:起卦是本地确定性纯领域逻辑 + +- 状态:`Accepted` +- 日期:2026-08-04 +- 决定:`CastEngine` 为纯 Kotlin,无随机、时间、网络、存储和问题文本依赖。 +- 原因:原始需求明确 AI 不参与起卦;纯函数最容易穷举验证和复现。 +- 后果:任何“个性化卦象”“AI 校正”“服务器起卦”均违反架构。 + +## ADR-004:用户真实投币并手动录入 + +- 状态:`Accepted` +- 日期:2026-08-04 +- 决定:MVP 不提供随机起卦、摇手机起卦或 AI 代投;每轮录入三枚“字/背”。 +- 原因:保留用户亲手完成过程的产品核心,并避免将流程游戏化。 +- 后果:可以改进录入控件,但不能增加默认随机按钮。 + +## ADR-005:固定 coin-v1 计值和 bottom-up 顺序 + +- 状态:`Accepted` +- 日期:2026-08-04 +- 决定:字=2、背=3;第一次为初爻;6/9 动、7/8 静;数据标准顺序 bottom-up。 +- 原因:传统资料在币面称呼上并不完全一致,项目需要可见且可复现的单一约定。 +- 后果:界面始终显示计值;改变约定必须增加方法版本,不能修改旧记录。 + +## ADR-006:东方文化采用内容优先的纸墨朱砂设计 + +- 状态:`Accepted` +- 日期:2026-08-04 +- 决定:以暖纸、墨色、克制朱砂、宋/黑体搭配和留白建立文化气质,同时保留原生 Android 交互语义。 +- 原因:流程和文字比装饰符号更能表达文化,也更利于阅读和无障碍。 +- 后果:拒绝龙凤祥云、金色发光、正文毛笔字、旋转太极和抽卡式动效。 + +## ADR-007:本地内容是核心,AI 是可关闭增强 + +- 状态:`Accepted` +- 日期:2026-08-04 +- 决定:64 卦、卦辞、爻辞和基础解释本地可用;AI 通过独立接口和功能开关接入。 +- 原因:核心流程要离线可靠,模型故障和成本不能阻塞产品。 +- 后果:P4 本地版可独立发布;AI 关闭时 UI 不能出现断裂占位。 + +## ADR-008:正式 AI 密钥只放后端 + +- 状态:`Accepted` +- 日期:2026-08-04 +- 决定:正式版经自有后端代理模型服务,不将开发者密钥打入 APK。 +- 原因:客户端秘密可以被提取;后端也承担限流、schema 校验、安全策略和版本控制。 +- 后果:没有后端之前只实现本地版或 mock,不用临时硬编码密钥“先跑起来”。 + +## ADR-009:历史记录采用明确保存、本机优先 + +- 状态:`Proposed` +- 日期:2026-08-04 +- 决定:用户完成后明确保存才进入 Room;保存时可不保留问题原文;默认无账号和云同步。 +- 原因:问题可能高度敏感,自动永久保存不是安全默认值。 +- 后果:历史功能不能成为完成起卦的前置条件。 + +## ADR-010:多动爻全部透明展示 + +- 状态:`Accepted` +- 日期:2026-08-04 +- 决定:显示所有实际动爻及其文本,不采用未经产品确认的规则隐藏或只选一条“主爻”。 +- 原因:不同解释传统存在差异,产品不应把一种裁决算法伪装成唯一事实。 +- 后果:AI 可以组织内容,但输入和界面保留全部动爻。 + +## 未决问题 + +| ID | 问题 | 推荐默认 | 阻塞阶段 | +|---|---|---|---| +| TBD-001 | 正式产品名与应用图标 | Brainwave 仅作代码代号 | P0 商店配置 | +| TBD-002 | application ID | 使用组织所有的反向域名 | P0 | +| TBD-003 | minSdk | 26;创建工程时复核覆盖率和依赖要求 | P0 | +| TBD-004 | 问题是否允许留空 | 允许选择“不写具体内容”,但需显式操作 | P3 | +| TBD-005 | 经典原文、现代白话的版本与授权 | 自有白话 + 可核验公版原文 | P2,发布阻塞 | +| TBD-006 | 乾用九、坤用六是否纳入 MVP | 内容具备时展示 | P2/P3 | +| TBD-007 | 历史是否默认保存卦象 | 默认不保存,完成后询问 | P4 | +| TBD-008 | Android Auto Backup 是否包含历史 | 默认排除敏感历史,待隐私评审 | P4 | +| TBD-009 | AI 模型供应商与自有后端 | 供应商无关接口;先交付本地版 | P5 | +| TBD-010 | 服务端问题/回复保留期 | 最小化且明确披露,优先不持久化正文 | P5,发布阻塞 | +| TBD-011 | 高风险本地资源表覆盖地区 | 首发市场确认后维护,不让模型编号码 | P5 | +| TBD-012 | 架构检查工具 | 选择维护活跃工具或小型自定义测试 | P0/P1 | + +## 新增决策模板 + +```markdown +## ADR-NNN:标题 + +- 状态:Proposed | Accepted | Superseded | Rejected +- 日期:YYYY-MM-DD +- 关联:需求、issue 或旧 ADR +- 决定:一句可执行结论 +- 原因:为什么现在这样选择 +- 备选:认真考虑过什么 +- 后果:实现、迁移、测试和文档影响 +- 复审条件:何时重新评估 +``` + +不要直接改写旧 ADR 来隐藏历史。需要反转时新增 ADR,并把旧记录标为 `Superseded by ADR-NNN`。 diff --git a/docs/domain-rules.md b/docs/domain-rules.md new file mode 100644 index 0000000..c544786 --- /dev/null +++ b/docs/domain-rules.md @@ -0,0 +1,163 @@ +# 领域规则:三枚铜币与六爻 + +> 状态:MVP 强制规范 +> 适用范围:所有起卦算法、显示模型、持久化模型和相关测试 + +本文件定义本项目唯一允许的计算规则。UI 文案、AI 输出和数据源都不能覆盖这些规则。 + +## 1. 术语和类型 + +建议使用有语义的封闭类型,避免裸 `Int` 在层间传播: + +```kotlin +enum class CoinSide(val value: Int) { + CHARACTER(2), // 字面 + REVERSE(3), // 背面 +} + +enum class LineValue(val sum: Int, val polarity: Polarity, val moving: Boolean) { + OLD_YIN(6, Polarity.YIN, true), + YOUNG_YANG(7, Polarity.YANG, false), + YOUNG_YIN(8, Polarity.YIN, false), + OLD_YANG(9, Polarity.YANG, true), +} + +enum class Polarity { YIN, YANG } +``` + +代码片段只表达契约,最终实现的包名和细节以[系统架构](architecture.md)为准。 + +## 2. 铜币计值约定 + +MVP 固定采用: + +- 字面 `CHARACTER` 计 2; +- 背面 `REVERSE` 计 3; +- 每轮三枚相加,结果只可能是 6、7、8、9。 + +传统资料对“正/反”“阴/阳”与 2/3 的命名存在不同约定,因此界面不得只写含糊的“正面/反面”。首次使用和投币页必须始终显示当前计值:“字 2,背 3”。如果未来允许切换约定,约定必须在第一轮之前锁定并写入结果;不得在六轮中途改变。 + +## 3. 爻值映射 + +| 和值 | 名称 | 本卦爻形 | 是否动爻 | 之卦爻形 | +|---:|---|---|---|---| +| 6 | 老阴 | 阴爻,断线 | 是 | 阳爻,实线 | +| 7 | 少阳 | 阳爻,实线 | 否 | 阳爻,实线 | +| 8 | 少阴 | 阴爻,断线 | 否 | 阴爻,断线 | +| 9 | 老阳 | 阳爻,实线 | 是 | 阴爻,断线 | + +强制不变量: + +- 偶数 6、8 为阴;奇数 7、9 为阳。 +- 只有 6 和 9 为动爻。 +- 之卦只翻转动爻;静爻保持不变。 +- AI、网络、时间、问题文本和设备状态均不得影响上述映射。 + +## 4. 六次录入与顺序 + +数组和数据库中的标准顺序一律为 **bottom-up**: + +| 数组索引 | 中文位置 | 投币轮次 | +|---:|---|---:| +| 0 | 初爻 | 1 | +| 1 | 二爻 | 2 | +| 2 | 三爻 | 3 | +| 3 | 四爻 | 4 | +| 4 | 五爻 | 5 | +| 5 | 上爻 | 6 | + +绘制界面时可以从屏幕顶部先画上爻,但只能在展示适配层反转;领域对象、序列化数据和测试夹具不得改成 top-down。 + +一个有效投币过程必须满足: + +- 恰好六轮; +- 每轮恰好三枚; +- 每枚只能是 `CHARACTER` 或 `REVERSE`; +- 生成结果后保留原始 18 枚输入,便于复核和审计。 + +## 5. 本卦与之卦 + +`CastResult` 至少包含: + +```text +methodVersion +coinConvention +rounds[6][3] +lineValuesBottomUp[6] +primaryPatternBottomUp[6] +movingLinePositions[] // 1..6,升序 +transformedPatternBottomUp[6] +primaryHexagramId // 文王卦序 1..64 +transformedHexagramId // 文王卦序 1..64 +contentVersion +createdAt // 只用于记录,不参与计算 +``` + +卦号不能通过“二进制数 + 1”推断。文王卦序不是简单二进制顺序,必须使用经过校验的“上卦 × 下卦”映射表或完整六爻模式映射表。 + +算法顺序: + +1. 将每轮三枚计值相加为 `LineValue`。 +2. 按录入顺序形成 bottom-up 六爻。 +3. 从各爻阴阳形成本卦模式并查得本卦编号。 +4. 收集值为 6 或 9 的位置作为动爻。 +5. 仅翻转动爻阴阳,形成之卦模式并查得之卦编号。 +6. 将全部原始输入、版本和确定结果构造成不可变 `CastResult`。 + +## 6. 读取内容的产品规则 + +MVP 采用透明而不过度裁决的显示策略: + +- 始终显示本卦卦辞。 +- 无动爻:明确显示“无动爻”;之卦与本卦相同,可弱化重复内容。 +- 有动爻:按初爻到上爻显示本卦中所有实际动爻的爻辞,并显示之卦。 +- 多个动爻:不由算法擅自选出“唯一主爻”,也不隐藏其他动爻。 +- 乾卦六爻皆九、坤卦六爻皆六时,若采用的数据版本含“用九/用六”,应作为特殊文本额外展示;是否纳入 MVP 见[决策记录](decisions.md#未决问题)。 + +AI 可以组织和解释这些材料,但不得改变显示集合。 + +## 7. 状态机 + +```text +DRAFT(question) + └─ start → CASTING(confirmedRounds = 0..5) + ├─ edit previous → CASTING + ├─ confirm sixth → SEALED(CastResult) + └─ cancel → DRAFT + +SEALED + ├─ request local explanation → EXPLAINED_LOCAL + ├─ consent + request AI → EXPLAINING_AI → EXPLAINED_AI | AI_FAILED + └─ explicit restart → DRAFT(newSessionId) +``` + +禁止从 `EXPLAINING_AI` 或 `EXPLAINED_AI` 回写 `CastResult`。重新起卦必须创建新的会话标识。 + +## 8. 必须存在的确定性测试 + +| 输入(初爻到上爻) | 本卦 | 之卦 | 动爻 | +|---|---|---|---| +| `7,7,7,7,7,7` | 乾 1 | 乾 1 | 无 | +| `8,8,8,8,8,8` | 坤 2 | 坤 2 | 无 | +| `9,9,9,9,9,9` | 乾 1 | 坤 2 | 1–6 | +| `6,6,6,6,6,6` | 坤 2 | 乾 1 | 1–6 | +| `7,8,8,8,8,8` | 复 24 | 复 24 | 无 | +| `9,8,8,8,8,8` | 复 24 | 坤 2 | 初爻 | + +此外必须: + +- 穷举 8 种三枚铜币排列,验证求和与 `LineValue`。 +- 穷举 4⁶ = 4,096 种六爻数值组合,验证长度、动爻和变换不变量。 +- 验证 64 种阴阳模式恰好映射到 64 个不重复卦号。 +- 验证序列化再反序列化不改变任何确定字段。 + +## 9. 版本规则 + +- 初始算法版本建议为 `coin-v1`。 +- 改变字/背计值、动爻规则、顺序或卦号映射都属于破坏性领域变更,必须新增版本和决策记录,不能静默覆盖历史结果。 +- 内容措辞变化只提升 `contentVersion`,不得改变 `methodVersion`。 + +参考资料仅用于交叉核验;仓库内上述规则才是实现事实源: + +- [三枚铜币产生 6/7/8/9,并自下而上记录](https://uaya.org/learn/iching/using-the-oracle/casting-techniques/three-coins/understanding-results/) +- [三枚铜币法的爻值与动爻说明](https://www.ichingonline.net/instruction.php) diff --git a/docs/failure-memory.md b/docs/failure-memory.md new file mode 100644 index 0000000..b2e059a --- /dev/null +++ b/docs/failure-memory.md @@ -0,0 +1,71 @@ +# 失败记忆与防回归台账 + +> 状态:初始风险基线;实现和运营过程中持续追加 +> 用途:把容易重复的错误转化为持久约束和机械反馈 + +## 1. 已知高风险模式 + +| ID | 失败模式 | 常见症状 | 持久护栏 | 验证 | +|---|---|---|---|---| +| FM-001 | 六爻顺序被反转 | 已知输入显示成另一卦;初爻画在顶部 | 领域统一 bottom-up,仅展示适配层反转 | 已知夹具 + 4,096 组合测试 + UI 语义测试 | +| FM-002 | 6/7/8/9 映射错误 | 6/9 未变或 7/8 被标成动爻 | `LineValue` 封闭枚举,禁止散落 `% 2`/magic number | 8 种币面与映射参数化测试 | +| FM-003 | 用二进制序号冒充文王卦序 | 阴阳形正确但卦号/卦名错误 | 经审核的完整模式映射表 | 64 模式唯一性与已知卦测试 | +| FM-004 | UI top-down 数据回流领域层 | 保存后再打开结果颠倒 | DTO 字段名强制 `BottomUp`,序列化往返测试 | Room/DTO round-trip | +| FM-005 | AI 参与或改写起卦 | AI 前后卦号变化;网络失败无结果 | `CastEngine` 纯 Kotlin;解释状态与结果分离;依赖检查 | 请求前后不可变测试、离线 E2E | +| FM-006 | 用户未点击就发起 AI 请求 | 进入结果页即出现网络流量 | 显式调用门与同意状态机 | fake server 调用次数为 0 | +| FM-007 | 生产密钥进入 APK | BuildConfig/strings/NDK 中出现 key | 后端代理、secret scan、APK 检查 | CI secret scan + release artifact scan | +| FM-008 | 敏感问题进入日志 | crash/HTTP 日志出现原文 | 结构化脱敏错误码;发布关闭 body logger | 日志捕获测试与人工抓取 | +| FM-009 | 内容缺失时数组错位 | 第 N 卦显示第 N+1 卦文本 | 以显式卦号查表;启动/构建期 schema 校验 | 缺失/重复条目负向测试 | +| FM-010 | 使用未授权现代译文 | 上架投诉或无法说明来源 | 每段 `sourceRefs` + 许可证清单 + 发布签核 | 内容校验 + 人工版权审核 | +| FM-011 | “国风”装饰损害可用性 | 低对比水墨、毛笔正文、小铜币按钮 | 语义令牌、48dp、对比度和反模式清单 | accessibility test + 真机评审 | +| FM-012 | 动爻只用朱砂色表示 | 色觉用户/读屏无法识别 | 颜色 + 形状/符号 + 文本 | TalkBack 和去色检查 | +| FM-013 | 旋转或进程重建重复提交 | 丢轮次、重复 AI 扣费 | SavedState、idempotency key、显式请求状态 | 重建/并发/取消测试 | +| FM-014 | AI 输出被当 HTML/命令执行 | 恶意链接、样式或脚本进入 UI | 结构化纯文本 schema、长度和字符校验 | 对抗性响应测试 | +| FM-015 | 高风险问题得到命令式答案 | 模型要求买卖、停药、立即分手 | 服务端安全提示、测试集、本地降级 | 高风险金丝雀用例 | +| FM-016 | 为通过测试关闭门禁 | ignored test、宽泛 catch、destructive migration | DoD 与评审规则,失败必须归因 | CI 检查 skipped tests/配置差异 | +| FM-017 | 文档与实现漂移 | 代理按旧命令/旧结构工作 | 文档状态、链接检查、行为变更同提交 | CI docs check + 里程碑熵清理 | +| FM-018 | 将实时 AI 当唯一测试 oracle | 测试不稳定、成本和输出漂移 | fake server + 固定 schema fixtures | JVM/integration tests 无外网依赖 | + +## 2. 故障登记模板 + +可复现且有再次发生价值的故障追加如下记录: + +```markdown +## INCIDENT-YYYYMMDD-NN:短标题 + +- 发现日期: +- 影响需求: +- 环境/版本: +- 用户症状: +- 最小复现: +- 根因: +- 为什么旧护栏没捕获: +- 修复: +- 新增测试/静态规则: +- 相关提交或 issue: +- 后续观察: +``` + +不要记录密钥、真实用户问题或完整 AI 回复;使用脱敏夹具。 + +## 3. 提升规则 + +- 同类问题第一次出现:修复并加回归测试。 +- 第二次出现:在本文件登记,并优先增加 lint、架构测试、schema 或聚合验证。 +- 影响领域确定性、隐私、密钥、内容授权或高风险 AI 的问题:第一次即登记并加入发布门禁。 +- 只写“以后注意”不算护栏;必须指出由什么测试、脚本、类型或评审入口阻止复发。 + +## 4. 定期审计问题 + +每个里程碑检查: + +1. 是否出现新的裸数字 2/3/6/7/8/9 业务逻辑? +2. 是否有非 `CastEngine` 代码重新推导卦象? +3. 是否有新增网络路径在用户同意前发送内容? +4. 是否有日志或测试夹具包含类似真实问题的文本? +5. 是否有卦辞、字体、图片缺少来源? +6. 是否有页面绕过主题令牌或无障碍语义? +7. 是否有依赖方向、测试命令或文档链接已经过期? +8. 是否有多次出现但仍只靠人工评审发现的问题? + +审计输出应形成小型、可评审的修复任务,而不是一次大规模无边界重写。 diff --git a/docs/implementation-plan.md b/docs/implementation-plan.md new file mode 100644 index 0000000..8f0946a --- /dev/null +++ b/docs/implementation-plan.md @@ -0,0 +1,196 @@ +# 分阶段实施计划 + +> 状态:未开始 +> 计划原则:先锁定确定性领域核心,再接内容和 UI,最后接网络 AI + +## 1. 依赖图 + +```text +P0 工程骨架 + ├── P1 起卦领域核心 ──→ P3 主流程 UI ──→ P4 本地完整 MVP + ├── P2 内容数据管线 ──→ P3 主流程 UI + └── P6 质量与发布(贯穿) + +P4 本地完整 MVP ──→ P5 AI 后端与解释 +``` + +P1 与 P2 可并行,但 P3 不能在领域与内容契约未稳定时复制临时算法或硬编码卦辞。 + +## 2. P0:仓库与 Android 骨架 + +目标:建立可构建、可测试、可导航的原生 Android 项目。 + +任务: + +- 基于 Google `android/architecture-templates` 的 `base` 分支初始化。 +- 确认正式应用名称、package/application ID、minSdk。 +- 配置 Kotlin、Compose、Material 3、Hilt、Room、DataStore、Navigation 和 Version Catalog。 +- 配置 Gradle Wrapper、格式化、lint、单元测试和 CI。 +- 建立 [系统架构](architecture.md)中的包结构和空 feature 边界。 +- 将根 `AGENTS.md` 设计为短地图,指向本目录和验证命令。 +- 增加 `verifyLocal` 聚合任务及基础 secret scan。 + +退出条件: + +- Windows 上一条命令可执行格式、lint、单测和 debug 构建。 +- CI 使用同一组命令。 +- 空应用可在模拟器启动,导航到占位欢迎页。 +- 没有生产服务密钥或真实内容。 + +## 3. P1:领域核心 + +目标:在纯 Kotlin 中完成并证明三枚铜币算法。 + +任务: + +- 建立 `CoinSide`、`LineValue`、`Polarity`、`CastRound`、`CastResult`。 +- 实现六轮输入校验和 `CastEngine`。 +- 建立经过双重校验的 64 卦模式映射表。 +- 实现之卦变换和动爻位置。 +- 实现版本化序列化 DTO。 +- 完成 8 种币面、4,096 种六爻、64 模式和已知夹具测试。 +- 增加 domain 无 Android/网络依赖的架构门禁。 + +退出条件: + +- [领域规则](domain-rules.md)全部被测试覆盖。 +- 测试无随机、无网络、无系统时间依赖。 +- `CastEngine` API 经评审后冻结为 `coin-v1`。 + +## 4. P2:内容数据管线 + +目标:建立可追踪、可校验、可发布的本地内容包。 + +任务: + +- 决定原文版本、现代白话来源和授权。 +- 实现 JSON schema、解析器和内容版本。 +- 录入/导入 64 卦、卦辞、384 条爻辞及所需特殊文本。 +- 建立来源清单、许可证清单和内容审核记录。 +- 实现构建期完整性校验与映射交叉校验。 +- 实现 `HexagramContentRepository` fake 与 assets 版本。 + +退出条件: + +- 64 卦/384 爻数据完整、唯一且来源可追踪。 +- 缺失、重复、非法顺序和错误映射测试均能失败。 +- 内容负责人确认可再分发。 + +## 5. P3:核心用户流程与东方设计系统 + +目标:完成离线起念、六次录入和结果阅读。 + +任务: + +- 实现颜色、字体、间距、形状和动画令牌。 +- 实现卦象 `Canvas`、动爻标记和读屏语义。 +- 实现首次说明、起念、投币和结果页面。 +- 实现 `CastingSessionViewModel` 状态机及 SavedState 恢复。 +- 支持前五轮返回修改、第六轮封印和明确重新起卦。 +- 接入本地内容并区分原文/本地白话。 +- 完成深浅主题、字体缩放、TalkBack、横屏和大屏适配。 + +退出条件: + +- 飞行模式可以从起念走到完整结果。 +- 已知夹具的屏幕卦象、名称、动爻和之卦一致。 +- AI/网络代码尚未存在也不影响流程。 +- [UX 与东方视觉](ux-design.md)检查表通过。 + +## 6. P4:本地解释与历史 + +目标:形成不依赖 AI 的完整 MVP。 + +任务: + +- 实现版本化本地解释模板。 +- 实现“可以试的一小步”的非裁决式结构。 +- 实现 Room 历史、可选保存问题、详情和删除。 +- 实现 DataStore 设置与同意版本基础设施。 +- 完成 migration、删除、隐私和离线测试。 + +退出条件: + +- 用户可选择不保存问题而保存卦象结果。 +- 本地解释在无网络时可用。 +- 删除行为与备份策略一致且经过验证。 +- 此阶段已经是可发布的本地版候选。 + +## 7. P5:AI 解读 + +目标:在不扩大起卦权限的前提下增加可控 AI 解释。 + +前置阻塞: + +- 模型提供商和后端部署方案确定; +- 隐私政策、数据保留和成本/限流规则确认; +- AI 安全用例集通过产品审核。 + +任务: + +- 建立后端代理、认证、限流、超时和脱敏日志。 +- 实现 `explanation-v1` 请求/响应 schema 与提示词版本。 +- Android 实现同意、请求、取消、重试和本地降级。 +- 对提示注入、高风险问题、非法输出和模型故障做测试。 +- 增加远程功能开关;关闭 AI 时本地版仍完整。 + +退出条件: + +- [AI 解释与安全](ai-safety.md)所有验收测试通过。 +- APK 无生产模型密钥。 +- 抓包显示只有用户明确动作触发请求,载荷与同意说明一致。 +- 服务端故障不会改变或隐藏 `CastResult`。 + +## 8. P6:质量、发布与运营 + +贯穿所有阶段: + +- 建立 CI、架构检查、内容校验、secret scan 和依赖许可证报告。 +- 建立脱敏崩溃监控和最小匿名指标。 +- 增加可复现 screenshot/accessibility 测试环境。 +- 准备隐私政策、内容来源、免责声明和应用商店素材。 +- 每次重复缺陷更新[失败记忆](failure-memory.md)和机械护栏。 +- 定期清理未使用依赖、重复 helper、过期文档和 TODO。 + +发布条件完全遵循[质量门禁](quality-gates.md#6-发布门禁)。 + +## 9. 编码任务模板 + +后续任务应使用以下结构,减少代理猜测: + +```markdown +## 目标 +一个可验证的结果。 + +## 范围 +- 允许修改:... +- 不在范围:... + +## 需求 +- FR-C-004 +- NFR-DET-001 + +## 必读 +- docs/domain-rules.md +- docs/architecture.md + +## 验收 +- Given/When/Then 场景 +- 要运行的具体命令 + +## 风险 +- 数据迁移 / 隐私 / 内容授权 / 无障碍 / 无 +``` + +任务应尽量小到单次变更可完整验证,不以“大致完成页面”作为验收描述。 + +## 10. MVP 切分建议 + +最稳妥的发布顺序: + +1. **内部算法版**:P0–P1,仅验证领域核心。 +2. **离线体验版**:P2–P3,给测试用户完成起卦与阅读。 +3. **本地正式候选**:P4,不依赖 AI 即可发布。 +4. **AI 增强版**:P5,通过后由功能开关逐步开放。 + +这样 AI、后端或供应商选择不会阻塞核心产品,也不会迫使客户端把密钥和高风险逻辑提前塞入 APK。 diff --git a/docs/product-spec.md b/docs/product-spec.md new file mode 100644 index 0000000..7055162 --- /dev/null +++ b/docs/product-spec.md @@ -0,0 +1,143 @@ +# 产品规格 + +> 状态:MVP 方案基线 +> 产品代号:Brainwave(正式名称 TBD) +> 原始输入:[原始需求.txt](原始需求.txt) + +## 1. 产品定义 + +Brainwave 帮助用户把一件正在纠结的事放慢来看。应用保留三枚铜币、六次成卦、本卦、之卦和动爻的文化结构,但把输出定位为反思材料,而不是预测、命令或替代决策。 + +核心承诺: + +1. 卦由用户的真实投币结果在本地确定。 +2. AI 只解释已经确定的结果,无法参与、重算或改写起卦。 +3. 用户在看到原始结果后,主动点击「解」才会触发解释。 +4. 解释尽量落到一项现实、低风险、可撤销的小行动。 +5. 应用不预测发财时间,不替用户决定辞职、分手、医疗、法律或投资行为。 + +## 2. 产品原则 + +| 原则 | 产品含义 | +|---|---| +| 人先于算法 | 用户亲手投币;应用只记录和计算 | +| 原文先于解释 | 先展示本卦、之卦、卦辞和动爻,再提供「解」 | +| 反思而非裁决 | 使用“可以观察/考虑”,不用“你必须/注定” | +| 本地优先 | 无网络时仍可完成输入、起卦、查看本地内容 | +| 明示边界 | AI 调用、数据发送、来源和失败状态都对用户可见 | +| 克制表达 | 东方文化氛围服务于阅读和节奏,不制造神秘权威 | + +## 3. 目标用户与使用情境 + +主要用户是希望通过传统文化框架整理想法、但不希望被“算命结果”替代判断的中文 Android 用户。 + +典型情境: + +- 在两个可行选择间犹豫,希望换一个角度观察。 +- 情绪复杂时,先慢下来描述问题,再决定下一步。 +- 对《易经》文化感兴趣,希望理解卦象和动爻的基本结构。 +- 想保存一次反思过程,日后回看当时的判断与行动。 + +本产品不适合作为紧急求助、医疗诊断、法律意见、投资建议或人身安全决策工具。 + +## 4. MVP 用户流程 + +```text +首次说明 + ↓ +起念:写下纠结之事 + ↓ +定法:确认“背=3、字=2”和由下而上 + ↓ +投币:真实投三枚,逐次录入,共六次 + ↓ +成卦:本地锁定本卦、之卦、动爻 + ↓ +观象:查看卦名、卦辞、动爻原文/本地白话 + ↓ +主动点击「解」 + ↓ +本地解释或明确同意后调用 AI + ↓ +留下一项低风险、可撤销的小行动 +``` + +用户可以在前五次录入期间返回修改;第六次确认后生成不可被 AI 修改的 `CastResult`。若要改变投币记录,必须明确选择“重新起卦”,旧结果不在原地静默变化。 + +## 5. 功能需求 + +### 5.1 起念 + +- **FR-Q-001** 应用允许用户输入当前纠结的问题,建议长度 1–200 个 Unicode 字符。 +- **FR-Q-002** 问题字段必须有可见标签和隐私说明,不能仅使用占位文字。 +- **FR-Q-003** MVP 不要求账号、手机号或身份信息。 +- **FR-Q-004** 问题在本地保存与否必须由用户可见的设置或保存动作决定,不得默认上传。 + +### 5.2 投币与成卦 + +- **FR-C-001** 用户每轮录入三枚铜币的“字/背”,共六轮。 +- **FR-C-002** 应用不提供随机、AI 或服务器代投功能。 +- **FR-C-003** 每轮即时显示该爻的数值和阴阳/动静含义,但不得提前生成完整卦名来影响后续录入。 +- **FR-C-004** 六爻按初爻至上爻、由下而上存储和绘制。 +- **FR-C-005** 第六轮确认后,本地生成稳定、可序列化的 `CastResult`。 +- **FR-C-006** 投币、求和、动爻变换和卦号映射必须符合[领域规则](domain-rules.md)。 +- **FR-C-007** 进程重建或屏幕旋转不得悄悄丢失已确认的投币进度。 + +### 5.3 结果展示 + +- **FR-R-001** 结果页必须显示六条原始爻、本卦名称和编号。 +- **FR-R-002** 有动爻时必须显示动爻位置、动爻文本和之卦;无动爻时明确显示“无动爻”。 +- **FR-R-003** 动爻不能只靠颜色区分,还必须有文字或形状标记。 +- **FR-R-004** AI 解释加载失败不得影响原始结果和本地内容的展示。 +- **FR-R-005** 本卦与之卦的内容必须来自版本明确、授权清晰的本地数据集。 + +### 5.4 解释 + +- **FR-E-001** 结果锁定且原始内容已经可见后,才显示可用的「解」操作。 +- **FR-E-002** 用户可以选择本地解释;AI 是可选增强,不是完成流程的前提。 +- **FR-E-003** 第一次发送给 AI 前,必须说明会发送哪些内容并获得明确同意。 +- **FR-E-004** AI 输出必须符合[AI 解释与安全](ai-safety.md)的结构和语言边界。 +- **FR-E-005** 解释必须将本卦、之卦和动爻视为输入事实,不得重新生成或纠正它们。 +- **FR-E-006** 最终行动建议必须低风险、可撤销、有限时,并保留“不采取行动”的选项。 + +### 5.5 历史记录 + +- **FR-H-001** 历史记录属于 MVP 后半段能力;核心起卦不依赖它。 +- **FR-H-002** 保存时必须允许用户选择是否保留问题原文。 +- **FR-H-003** 删除记录必须可撤销或经过确认。 +- **FR-H-004** 未获得单独授权时,历史记录只保存在应用私有存储中。 + +## 6. 非功能需求 + +- **NFR-OFF-001** 写问题、录入、起卦、查看本地卦辞在飞行模式下可用。 +- **NFR-DET-001** 相同的六次投币输入和同一数据版本必须产生完全相同的本卦、动爻和之卦。 +- **NFR-SEC-001** 开发者 AI 密钥不得进入 APK、源码仓库或客户端日志。 +- **NFR-PRI-001** 问题文本、解释全文和模型请求不得写入分析埋点或崩溃日志。 +- **NFR-A11Y-001** 正文对比度至少 4.5:1;交互目标至少 48×48dp;支持系统字体缩放和读屏。 +- **NFR-PERF-001** 本地起卦为纯内存同步计算,用户确认后应即时完成,不显示伪加载动画。 +- **NFR-RES-001** 旋转、切后台和可恢复的进程重建后保留当前流程状态。 +- **NFR-I18N-001** 首版以简体中文为基线,所有用户文案从资源文件读取,不硬编码在 Composable 中。 + +## 7. MVP 非目标 + +- 自动、摇手机或 AI 随机起卦。 +- 根据日期、位置、设备传感器或用户画像改变卦象。 +- 社区、排行榜、连续签到、灵验率或“改善运势”机制。 +- 付费解锁“更准”结果、制造焦虑的倒计时或重复付费抽取。 +- 医疗、法律、投资、博彩、婚恋或职业决定的确定性建议。 +- 用 AI 自动决定应该读取哪一爻或隐藏用户已经得到的动爻。 +- 首版账号体系、云同步和社交分享图生成。 + +## 8. 成功标准 + +MVP 可以交付的最低条件: + +1. 新用户不阅读帮助也能正确完成六次录入。 +2. 所有 4,096 种六爻数值组合均能确定本卦和之卦,且满足领域不变量。 +3. 断网时完整完成本地流程。 +4. AI 被禁用、超时或返回非法内容时,原始结果仍然完整且可阅读。 +5. 用户能清楚分辨“经典/本地内容”“AI 解释”和“建议行动”。 +6. 无障碍检查覆盖投币控件、卦象、动爻和长文阅读。 +7. 64 卦及爻辞内容通过完整性、来源和授权门禁。 + +需求与验证方式的对应关系见[质量门禁](quality-gates.md#4-需求追踪矩阵)。 diff --git a/docs/quality-gates.md b/docs/quality-gates.md new file mode 100644 index 0000000..44a72d0 --- /dev/null +++ b/docs/quality-gates.md @@ -0,0 +1,156 @@ +# 质量门禁与验证策略 + +> 状态:测试策略已定义;命令在 Android 骨架初始化后启用 +> 原则:完成必须有可重复证据,不能以“代码看起来正确”代替验证 + +## 1. 反馈循环 + +每个实现任务遵循: + +```text +需求编号 → 最小实现 → 就近测试 → 全量静态/单元检查 → 设备验证 → 文档同步 + ↑ ↓ + └──── 失败归因与修复 ────┘ +``` + +同类失败第二次出现时,应更新[失败记忆](failure-memory.md);适合机械检查的规则必须转成测试、lint 或脚本。 + +## 2. 预期本地命令 + +工程创建后,Windows 环境至少提供以下稳定入口: + +```powershell +.\gradlew.bat spotlessCheck +.\gradlew.bat lintDebug +.\gradlew.bat testDebugUnitTest +.\gradlew.bat assembleDebug +.\gradlew.bat connectedDebugAndroidTest +``` + +若采用不同格式化插件,命令可以调整,但必须在本文件和 CI 同步更新。`connectedDebugAndroidTest` 需要模拟器或设备,应与纯 JVM 快速门禁分开。 + +建议再提供聚合任务: + +```powershell +.\gradlew.bat verifyLocal +``` + +它至少依赖格式、lint、JVM 单元测试和 debug 构建,使代理不必猜测正确验证组合。 + +当前仓库没有 Gradle Wrapper,所以上述命令尚未运行,也不能报告为通过。 + +## 3. 测试层次 + +### 3.1 纯 JVM 领域测试(最快,阻塞合并) + +- 三枚铜币 8 种排列映射。 +- 六爻 4,096 种组合不变量。 +- 64 卦模式映射唯一性。 +- bottom-up 顺序和显示适配。 +- `CastResult` 序列化往返。 +- 本地内容 schema 与完整性。 +- 本地解释模板选择。 +- AI DTO schema、长度和纯文本校验。 + +### 3.2 ViewModel/Repository 测试(阻塞合并) + +- 投币状态机、返回修改和第六次封印。 +- SavedState 恢复。 +- 只在用户点击且同意后调用 AI。 +- 并发点击、取消和超时不重复提交。 +- Room 保存/读取一致性。 +- 删除与撤销/确认行为。 + +测试优先使用接口的 fake 实现,不依赖真实网络和实时模型。 + +### 3.3 Compose UI 测试(阻塞发布) + +- 首次说明与方法约定可达。 +- 六轮录入的进度、按钮启用状态和错误恢复。 +- 结果页本卦/之卦/动爻语义。 +- AI 同意、加载、失败、本地降级。 +- 最大字体下关键操作可到达。 +- TalkBack 语义、焦点顺序和触控目标。 +- 旋转/横屏/大屏布局。 + +### 3.4 端到端与人工验收(阻塞发布) + +- 飞行模式走完整核心流程。 +- 真机上检查输入法、返回手势、深浅主题和触觉。 +- 使用已知六爻夹具人工核对卦象绘制和文本。 +- 后端 staging 环境验证同意、超时、限流、非法响应和安全分支。 +- 验证安装包不包含生产密钥和未授权内容。 + +## 4. 需求追踪矩阵 + +| 需求 | 主要自动化证据 | 人工证据 | +|---|---|---| +| FR-Q-001~004 | ViewModel + Compose 输入测试 | 中文输入法、隐私文案 | +| FR-C-001~003 | 状态机与 UI 测试 | 真实投币录入可理解性 | +| FR-C-004~006 | 领域穷举与映射测试 | 已知卦象目视复核 | +| FR-C-007 | SavedState/重建测试 | 旋转、切后台、进程恢复 | +| FR-R-001~003 | UI 语义与 screenshot 测试 | TalkBack、色觉与长文阅读 | +| FR-R-004 | 网络失败测试 | 飞行模式 | +| FR-R-005 | 内容 schema/授权清单检查 | 内容负责人签核 | +| FR-E-001~003 | 网络调用次数与同意状态测试 | 首次同意流程 | +| FR-E-004~006 | 输出 schema + 安全用例集 | 安全/产品审核 | +| FR-H-001~004 | Room、删除、权限边界测试 | 隐私设置与删除体验 | +| NFR-OFF-001 | fake/offline 集成测试 | 飞行模式真机 | +| NFR-DET-001 | 4,096 组合属性测试 | 无 | +| NFR-SEC/PRI | secret scan、日志测试 | APK/代理抓包复核 | +| NFR-A11Y-001 | Compose accessibility checks | TalkBack、最大字体 | +| NFR-RES-001 | 重建和恢复测试 | 厂商设备抽测 | + +## 5. 架构与数据门禁 + +工程初始化时应新增可机械执行的规则: + +- domain 包不得依赖 Android、Compose、Room 和网络包。 +- `data.ai` 不得依赖或调用 `CastEngine`。 +- feature UI 不得引用 DAO 或网络 DTO。 +- 生产源码不得出现 API key 形态、真实用户问题测试样例或 HTTP body logger。 +- 内容校验任务验证 64 卦、384 条爻辞、唯一模式、来源和许可证字段。 +- 文档链接和需求编号无悬空引用。 + +可以使用现有静态工具、架构测试库或小型自定义 Gradle 任务;具体选型写入[决策记录](decisions.md)。规则的错误消息应告诉代理如何修复,而不只报告失败。 + +## 6. 发布门禁 + +发布候选必须满足: + +- [ ] 格式、lint、JVM 测试、UI 测试和 release 构建全部通过。 +- [ ] 64 卦内容校验与授权清单通过。 +- [ ] 飞行模式核心流程通过。 +- [ ] AI 安全测试集和故障降级通过;若 AI 未上线则功能关闭且不影响本地流程。 +- [ ] 无生产密钥、敏感日志和真实用户内容进入 APK/测试产物。 +- [ ] 浅色/深色、小屏/大屏、最大字体、TalkBack、减少动态效果完成抽测。 +- [ ] 隐私政策和应用内数据说明与实际网络行为一致。 +- [ ] 数据库迁移、备份策略和删除行为已验证。 +- [ ] 已知阻塞缺陷为 0,非阻塞缺陷有记录和责任人。 + +## 7. 完成定义(Definition of Done) + +一个任务只有在以下条件全部满足时才算完成: + +1. 关联明确的需求或缺陷编号。 +2. 实现遵守领域、架构、UX 和 AI 边界。 +3. 新行为有自动化测试,缺陷有回归测试。 +4. 执行了与风险相称的验证命令并记录结果。 +5. 失败不是通过跳过测试、降低断言或吞异常来“解决”。 +6. 用户可见行为或契约变化已经同步文档。 +7. 没有引入秘密、未授权内容和敏感测试数据。 +8. 交付说明列出变更、验证证据、剩余风险和未执行项。 + +## 8. 验证报告模板 + +```markdown +### 验证 +- 需求:FR-C-004, FR-C-006 +- 已运行:`.\gradlew.bat testDebugUnitTest` +- 结果:通过,N tests +- 未运行:`connectedDebugAndroidTest`(原因:无可用模拟器) +- 人工检查:输入 9,8,8,8,8,8,得到复 24 → 坤 2,初爻动 +- 剩余风险:无 / 明确列出 +``` + +不得把未运行写成“应当通过”。 diff --git a/docs/ux-design.md b/docs/ux-design.md new file mode 100644 index 0000000..1e381f7 --- /dev/null +++ b/docs/ux-design.md @@ -0,0 +1,197 @@ +# UX 与东方视觉规范 + +> 状态:MVP 设计基线 +> 适用范围:Android 手机优先,兼顾横屏、平板和系统无障碍设置 + +## 1. 体验定位 + +界面要让用户感到“安静、清楚、有分寸”。东方文化来自流程、文字、材料感和留白,不来自堆叠符号。 + +产品体验不是: + +- 制造神秘权威的算命工具; +- 抽卡、开盲盒或“再测一次”的成瘾循环; +- 复刻古籍页面而牺牲现代触控和可读性。 + +产品体验是: + +- 一段从起念、投币、观象到落事的缓慢反思流程; +- 原始文化材料与现代解释层次分明; +- 保留用户自主判断,随时可以退出、不保存或不调用 AI。 + +## 2. 信息架构 + +MVP 顶层不需要底部导航。主流程是线性的,使用单 Activity 和清晰的返回行为: + +```text +欢迎/方法说明 +└── 起念 + └── 投币(1/6…6/6) + └── 结果 + ├── 本地解释 + └── AI 解释(需同意) + +次级入口 +├── 历史(启用后) +├── 方法说明 +├── 内容来源 +└── 设置与隐私 +``` + +投币流程返回时保留已经确认的轮次;从结果页返回不能导致重新计算。预测性返回手势必须与系统导航兼容。 + +## 3. 场景与页面契约 + +### 3.1 欢迎与方法说明 + +目的:在第一次起卦前建立边界和方法。 + +必须说明: + +- “你投币,应用记录;AI 不参与起卦。” +- “字为 2,背为 3;第一次是初爻,由下而上。” +- “结果用于整理想法,不替你做决定。” + +主操作只有一个:“开始”。“查看方法”是次级文本操作,不制造必须完成的教程轮播。 + +### 3.2 起念 + +页面标题可使用“此刻,你在为何事迟疑?”。问题输入框有固定标签“正在纠结的事”,辅文提示尽量描述事实、选择和顾虑,不要求生日、性别等无关信息。 + +交互规则: + +- 多行输入,支持系统输入法和字体缩放; +- 离开页面时在会话内保留草稿; +- 默认不把问题发送到网络; +- 用户可选择“不写具体内容,直接开始”,最终是否允许空值见[决策记录](decisions.md#未决问题)。 + +### 3.3 投币 + +页面始终显示: + +- 当前轮次,如“第三爻 · 3/6”; +- 三个独立的铜币录入控件; +- “字 · 2 / 背 · 3”的当前约定; +- 已确认的爻从下向上累积显示; +- 一个主按钮“确认这一爻”。 + +铜币控件不得仅靠拟真图片表达正反面;图形旁必须有“字/背”文本和选中语义。三个控件触控区域均不小于 48×48dp。 + +每次确认可以有一次轻触觉反馈和 150–250ms 的爻线出现动画。不得使用持续摇晃、金币飞散、音效倒计时或强制等待。开启“减少动态效果”时直接更新状态。 + +### 3.4 成卦与观象 + +信息优先级: + +1. 本卦卦象、编号和名称; +2. 动爻位置; +3. 之卦(若与本卦不同); +4. 卦辞与实际动爻文本; +5. 「解」按钮。 + +卦象使用 Compose `Canvas` 或稳定的 Compose 图元绘制,不使用包含文字的位图。无障碍描述示例:“第 24 卦,复;初爻阳,其余为阴;初爻为动爻”。 + +原文、本地白话和 AI 解释必须有明确标签,不使用视觉相似但来源不明的段落混排。 + +### 3.5 解读与落事 + +「解」是结果页唯一主操作。点击后先选择或显示当前方式: + +- “本地解读”:立即、离线,不发送问题; +- “AI 解读”:说明将发送问题、本卦、之卦和动爻文本,首次需要明确同意。 + +AI 加载超过 300ms 时显示内联进度;按钮在请求期间禁用,避免重复提交。超时后显示“保留当前结果,可重试或改用本地解读”,不能把结果页替换为空白错误页。 + +结尾固定使用“可以试的一小步”区域,包含时间范围和撤销方式;它是建议,不是判词。 + +## 4. 视觉系统 + +### 4.1 风格关键词 + +内容优先、纸张感、墨色、高留白、细线、克制朱砂、现代中文排版。 + +避免:龙凤、满屏祥云、旋转太极、金色发光、仿古卷轴、低对比度水墨、正文毛笔字、伪造印章和无意义繁体字。 + +### 4.2 颜色令牌 + +以下是起始令牌,不允许在页面中散落硬编码颜色: + +| 语义 | 浅色主题 | 用途 | +|---|---|---| +| `background` | `#F7F2E8` | 暖纸背景 | +| `surface` | `#FFFDF7` | 正文和卡片表面 | +| `onBackground` | `#1F1B16` | 墨色主文字 | +| `onSurfaceVariant` | `#655E55` | 次级说明 | +| `primary` | `#8C2F2B` | 朱砂主操作、动爻强调 | +| `onPrimary` | `#FFFFFF` | 主操作文字 | +| `secondary` | `#765D3E` | 旧铜色次级强调 | +| `outline` | `#B9AEA0` | 分隔线和输入边界 | +| `error` | Material 语义错误色 | 错误,不与朱砂强调混用 | + +深色主题不是简单反色。建议使用近墨背景、暖白正文和降低饱和度的朱砂色,并独立验证所有对比度。最终令牌以无障碍测试结果为准。 + +### 4.3 字体与排版 + +- 标题、卦名、短卦辞可使用授权清晰的思源宋体/Noto Serif CJK 子集。 +- 按钮、输入、长篇说明和系统信息使用系统中文无衬线或思源黑体。 +- 毛笔字体只允许出现在经过审查的品牌字形中,不能用于正文和关键操作。 +- 正文基准不小于 16sp,行高约 1.5–1.7;长文在平板上限制行宽。 +- 所有字号通过 `MaterialTheme.typography` 语义令牌提供,不在 Composable 中硬编码。 + +字体资源必须评估 APK 体积、授权和字形覆盖;不能为了视觉一致性下载不明来源字体。 + +### 4.4 形状与材质 + +- 使用 4/8dp 间距体系,页面以 24dp 左右的宽松留白为主。 +- 卡片圆角克制,避免所有内容都成为悬浮大圆角卡片。 +- 阴阳爻线、分隔线和图标使用统一粗细。 +- 纸纹若使用,透明度必须极低、可移除,且不得影响滚动性能与文字对比度。 +- 图标使用同一套矢量图标;Emoji 不作为结构性图标。 + +## 5. 动画与触觉 + +动画必须表达因果:确认一轮后,对应爻线从下向上出现;从本卦到之卦时,仅动爻发生形态过渡。 + +- 触控反馈在 100ms 内出现。 +- 微动画通常为 150–300ms,复杂过渡不超过 400ms。 +- 一屏同时动画的重点元素不超过两个。 +- 动画可中断,不阻止返回和触控。 +- 系统减少动态效果时关闭非必要过渡。 +- 触觉只用于确认单爻、完成六爻和显式错误,不对滚动或普通切换连续震动。 + +## 6. 无障碍验收 + +- 所有交互目标至少 48×48dp,邻近目标间距至少 8dp。 +- 正文与背景对比度至少 4.5:1;大图形和非文字元素至少 3:1。 +- 动爻同时使用颜色、形状/符号和文字说明。 +- 读屏顺序与视觉顺序一致;卦象有完整语义描述,装饰纸纹不进入语义树。 +- 最大系统字体下无关键文字截断,主按钮仍可见或可滚动到达。 +- 横屏、小屏和大屏不产生水平滚动;长文在平板上限制阅读宽度。 +- 错误信息靠近问题控件,包含原因和恢复动作。 + +## 7. 文案规范 + +推荐语气:平实、留有余地、说明来源。 + +| 避免 | 推荐 | +|---|---| +| “此卦预示你一定会成功” | “这段材料提醒你留意……” | +| “现在绝不能辞职” | “在不可逆决定前,可以先验证一个较小假设” | +| “AI 大师解卦” | “AI 解读” | +| “再算一次改变运势” | “重新开始一段记录” | +| “结果加载中”用于本地计算 | 本地结果即时展示 | + +解释中的经典文本、编辑撰写的本地白话、AI 生成内容必须分别标注。 + +## 8. UI 完成检查表 + +- [ ] 每屏只有一个主操作。 +- [ ] 投币进度和计值约定始终可见。 +- [ ] 卦象不是包含文字的位图。 +- [ ] 动爻不只靠颜色表达。 +- [ ] 本地与 AI 内容来源可辨认。 +- [ ] 加载、空、离线、超时和非法响应均有恢复路径。 +- [ ] 浅色、深色、最大字体、读屏、减少动态效果均验证。 +- [ ] 无龙凤祥云等与功能无关的“古风贴图”。 + +本规范采用内容优先、纸张阅读、低动态和宽松密度的移动端设计原则,并结合 Android 原生交互要求。工程实现仍应遵循 [Material 3](https://m3.material.io/) 与 [Compose 无障碍](https://developer.android.com/develop/ui/compose/accessibility)的最新官方指南。 diff --git a/docs/原始需求.txt b/docs/原始需求.txt new file mode 100644 index 0000000..f3c774c --- /dev/null +++ b/docs/原始需求.txt @@ -0,0 +1,3 @@ +写下一件正在纠结的事,亲手抛三枚铜币,六次成卦,然后看本卦、之卦、卦辞和动爻。点「解」以后,才会用本地内容或者 AI 把这些东西翻译成人话。 + +AI 不参与起卦。卦是本地生成的,AI 只负责解释已经发生的结果,最后尽量收在一件现实中能做、而且可以反悔的小事上。它不会告诉你什么时候发财,也不会替你决定要不要辞职、分手或者买股票。 \ No newline at end of file