diff --git a/CLAUDE.md b/CLAUDE.md index 3e5dee3..634fac0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -177,3 +177,21 @@ H5 页面通过特定的 JavaScript 协议初始化桥并注册自己的 handler - 改动已通过最小验证(BuildProject 通过 / 关键路径手测通过 / 单测通过)→ 立即提议 - 工作树还有其它无关待提交内容 → 先帮用户分类、按职责拆 commit,**不混提** - 用户未明确同意前不直接执行 `git commit`,但应当**清晰地告诉用户"现在适合提交了"**,并附上建议的 commit message + +--- + +## 进度同步规范 + +**每完成一个 Phase 子项(如 1.10 / 1.11 / 1.12 ...)必须立即更新 `docs/Development-Plan.md` 的进度标记**,不允许"代码提交但 Plan 未同步"的滞后。具体: + +- Plan §5 各 Phase 任务清单:完成的子项把 `- [ ]` 改 `- [x]`,附简短结果或耗时 +- Plan §8 进度追踪 checklist:同步勾选 + 简短补充(如算法名 / 端到端测试结果) +- 时机:通常与该子项的 `git commit` 同一 commit 内完成,commit message 末尾备注"Plan 进度已勾选" +- 若改动跨多个 Phase 子项(极少见),逐项勾选不能漏 +- 当任务清单中的描述与最终实现有差异(如新增子任务、合并子任务、改变实现策略)时,**同步修改任务描述**,不允许 Plan 文档与代码长期不一致 + +理由: +- Plan 是项目记忆的唯一权威进度视图,新人 clone 后只看 Plan §8 就能立刻知道整体进度 +- 若代码已完成但 Plan 未勾选,新人会误判项目状态、做重复工作 +- 若 Plan 描述与代码实现不一致,未来 Phase 验收 / 回归测试时会基于错误前提 +- 与 commit message 双写互为校验:commit log 是细节,Plan 是全局视角 diff --git a/docs/Development-Plan.md b/docs/Development-Plan.md index 17b0e43..a596845 100644 --- a/docs/Development-Plan.md +++ b/docs/Development-Plan.md @@ -262,23 +262,23 @@ Contract Design Plan(本文档) ##### 1.D 远程配置 + 版本对比 + zip 升级(**Phase 1 关键缺口,ADR-008**) -- [ ] **1.10** 实现 `ylgamehall/Source/Network/RemoteConfigClient.swift`(actor) +- [x] **1.10** 实现 `ylgamehall/Source/Network/RemoteConfigClient.swift`(actor) - URL 构造:`http://` + `BundleConfig.shared.gameConfig.replacingOccurrences("-", "/")` + `.txt`(**注意**:不带 `SERVERNew` 前缀,原 `daoqi/NewRootVC.m:250` 就是这样构造) - 拉远端 `.txt` 内容(实际是 JSON)→ `URLSession.async data(from:)`,10 s 超时 - 指数退避重试(msext 用 4s 等间隔暴力 timer,新外壳用 1s / 2s / 4s 三次) - 反 JSON → 强类型 `RemoteConfig` Codable 模型(嵌套:`agentlist[].channellist[].marketlist[]` + `agentlist[].gamelist[].channellist[].marketlist[]`) - 单测:mock URLSession + fixture JSON 验证嵌套解析正确 -- [ ] **1.11** 实现 `ylgamehall/Source/Network/VersionResolver.swift`(纯函数 + 单测) +- [x] **1.11** 实现 `ylgamehall/Source/Network/VersionResolver.swift`(纯函数 + 单测) - 输入:`RemoteConfig + (agentId, channelId, marketId, gameId)` 四组 ID - 输出:`ResolvedVersion { appVersion, appDownload, gameVersion, gameZip }` - 算法:4 级覆盖合并 —— 顶层 → 当前 agent → 当前 channel → 当前 market(最深胜出);agent 子树和 game 子树分别合并,game 子树最终覆盖 agent 子树 - 参考 `daoqi/NewRootVC.m:1372-1492` 的散落 if/else,新外壳实现成纯 reduce - 单测:覆盖所有边界(顶层有/无、深层缺失、agent vs game 优先级 4 组场景至少 12 用例) -- [ ] **1.12** 实现 `ylgamehall/Source/Resource/LocalVersionReader.swift`(nonisolated namespace) +- [x] **1.12** 实现 `ylgamehall/Source/Resource/LocalVersionReader.swift`(nonisolated namespace) - `localAppVersion: Int`:从 `BundleConfig.shared.appVersion` 读,转 Int - `localGameVersion: Int`:读 `Library/Caches/{gamedir}/{gamestart}/version.xml` 用 `XMLParser` 解析 `/game/version@value` 转 Int;缺失返回 0 - 单测:fixture xml 验证解析;缺失 / 损坏 xml 返回 0 -- [ ] **1.13** 实现 `ylgamehall/Source/Resource/LobbyZipUpgrader.swift`(actor) +- [x] **1.13** 实现 `ylgamehall/Source/Resource/LobbyZipUpgrader.swift`(actor) - `upgradeIfNeeded(remote: ResolvedVersion) async throws -> UpgradeOutcome` - 比较 `remote.gameVersion > LocalVersionReader.localGameVersion` → 触发下载,否则直接返回 `.noop` - 下载链路:`URLSession.download(from:)` → 临时文件 `tmp/lobbyzip-{uuid}.zip` → ZIPFoundation 解压到 `tmp/lobbyzip-{uuid}/` → **原子 rename** 到 `lobbyRoot`(解压前清空旧 lobbyRoot;不学 msext "目录名 +1" hack) @@ -781,10 +781,10 @@ H5 调 `OpenurlTitleData` 打开弹层 WebView,弹层内 H5 用 `window.settin - [x] 1.7 BridgeBus - [x] 1.8 WVJB JS 协议 - [x] 1.9 BridgedWebView -- [ ] 1.10 RemoteConfigClient(actor,URLSession async + 重试) -- [ ] 1.11 VersionResolver(纯函数 4 级覆盖 + 单测) -- [ ] 1.12 LocalVersionReader(version.xml 解析) -- [ ] 1.13 LobbyZipUpgrader(actor,原子 rename 升级) +- [x] 1.10 RemoteConfigClient(actor,URLSession async + 重试 + cache-busting + 短文本 + FlexibleString) +- [x] 1.11 VersionResolver(chulishengji 双子树合并算法,纯函数) +- [x] 1.12 LocalVersionReader(version.xml 解析 + ATS 全局放行) +- [x] 1.13 LobbyZipUpgrader(actor,原子 rename 升级;端到端实测 260→261) - [ ] 1.14 WebContainerViewController(16:9 letterbox + 串接整链路) - [ ] 1.15 VibratorHandler - [ ] 1.16 SceneDelegate → WebContainerViewController