主题
RVH 并入 RB 仓 —— 实施计划
物理仓库位置:
~/reading-browser(RB 桌面端)。对端 RVH =~/reading_vocab_helper(Flutter/Dart)。 创建:2026-08-28 · 立项自rvh-merge-evaluation.md(评估与论证在那份,本份只管怎么做)。✅ 已完成并归档(2026-08-29)
五个阶段全部收工,T4-1 ~ T4-10 十项全 ✅。 最后一项 T4-9(老 gitee 仓归档) 由用户于 2026-08-29 在 gitee 置为关闭状态(继承只读 + 冻结元数据;仓未删 —— 它仍是 tag
pre-rb-merge的宿主与最后回滚源)。本文件自此是历史记述,不再是进度真相源。 下面保留的全部内容都是「当时怎么想、 实测发现了什么、哪几条计划被推翻」的记录,不要照着它当操作指南 —— 里面的交接 prompt、
~/reading_vocab_helper路径、sync-rvh-vocabulary.sh等都是双仓时期的写法。合仓沉淀下来的活的东西在这些地方,改动去那里、不要改本文件: 红线 #5i/#6d/#9/#10 → 根
CLAUDE.md§4 · RVH 落地形状 →rvh/CLAUDE.md· 机械守卫 →scripts/cross-end-check.sh/scripts/check-vocab-asset.sh/.github/workflows/ci-cross-end.yml/ci-rvh.yml· as-built 日志 →CHANGELOG.md。🔴 本计划有四处「正文写的与实测不符」,后来会话若引用请连同订正一起看: T4-1 编号复原改判为消歧 · T4-4 统一编号改判为不统一 · T4-5 的收益模型被推翻 (git 本来就按内容去重,省的不是仓体积)· T4-6 两格处置各错一半,方向相反。 各自的「结果」段里都写明了推翻的证据。
以下为实施期间的原始记述,保持原样。
状态(2026-08-28 当时):阶段 0 ✅ · 阶段 1 ✅ · 阶段 2 ✅ 收工(T2-1 ~ T2-5)。 阶段 3 开工前先复核 RVH 侧
ahead=0(2026-08-28 收工时为 0,tagpre-rb-merge=7ffb05d), 并注意 T3-2 已被 T2-3 裁定加了两条--path(清USER_NOTES.md与那份 PDF 的历史)。阶段 1 独立成立, 不继续合并也已受益:根CLAUDE.md73015 → 50557 B(-30.8%), 新增src-tauri/CLAUDE.md+src/CLAUDE.md按需叠加,可无限期停在此处。 P0-2 判定 A(按需加载),收益模型的地基成立,但带一条限定:注入挂在文件工具上,cat/head/grep读同一个文件不触发(见 §2 P0-2 结果段)。该限定已落进阶段 1 的设计: 根 §4 保留全量红线索引(含已下沉那 16 条),根顶部路由表明写「走 Bash 时要显式读一次子文件」。 P0-3 查出的 Baidu OCR 凭据已定级为卫生问题(gitee 仓私有 + 该服务已弃用), 收尾转backlog.md的「百度 OCR 弃用收尾」条目,不阻塞本计划; 其中「清历史」一项已裁定在 T3-2 顺手做。 (实施期间)本文件是跨会话实施的唯一进度真相源。 每完成一个任务必须回写 §1 台账。本文不含红线锁标记,是刻意的(
/arch-checkS28 基线守恒)。合并落地后需要收敛的红线, 按 CLAUDE.md §4「红线归属规则」写进稳定归属,不留在本文件。
0. 冷启动开工指引(新会话先读这段)
你如果是一个没有上下文的新会话,按顺序做三件事:
- 读 §1 台账,找到第一个不是 ✅ 的任务。
- 读该任务所在阶段的阶段前言(讲清这一阶段的不变量与离场条件)。
- 读该任务本身。每个任务都写了「在哪个会话做 / 前置 / 动作 / 验收 / 回滚」, 不需要回看创建它的那次对话。
三条硬约束,任何会话都不许违反:
- A. 阶段 2 必须在 RVH 会话做(
~/reading_vocab_helper,Flutter/Dart 工具链)。 阶段 0/1/3/4 在 RB 会话做。CLAUDE.md §9「会话隔离规则」在合并完成前照旧有效。 - B. 阶段 3 是原子区(T3-2 到 T3-9)。中途停下来 = 仓库处于半合并状态,main 会红。 开工前确认你有一段完整时间,且没有其他会话在动这两个仓。
- C. 每个任务做完先跑验收命令,再回写 §1 台账,再提交。 顺序反了会留下"做没做完看不出来"的状态 ——那正是
docs/plans/README.md点名的病。
禁止事项(冷启动最容易踩的):
| 禁止 | 为什么 |
|---|---|
| 改写 RB 的 git 历史(filter-repo / rebase / force push) | 红线 #11 的 src-tauri/src/db/shipped_migrations.txt 真相源是发布 tag,改写会让 tag SHA 全变。只允许对 RVH 侧的临时克隆动 filter-repo |
用 git subtree add 合入 | 会丢掉路径前缀化的历史,见 T3-2 的实测证据 |
把 RVH 的镜像红线原样搬进 rvh/CLAUDE.md | 那只是把第二本账换个位置继续记,收益归零。见 T3-8 |
改 docs/plans/archive/ 与 docs/cross-end/ 里的历史记述 | 它们是写作当时的事实记录。只改操作性文件(脚本 / skill / CLAUDE.md / 活跃 plan / README 索引),见 T4-1 |
| 阶段 1 与其他会话并行 | 根 CLAUDE.md 是枢纽文件,CLAUDE.md §8 路径 A/B 都会命中 |
1. 进度台账
完成一个任务:把 ⬜ 改成 ✅,填日期与 commit 短 SHA。放弃/改判的写 ⏭ 并注明原因。
阶段 0 · 预检与准备(只读 + 装工具,零风险,可随时中断)
| ID | 任务 | 会话 | 状态 | 日期 / commit |
|---|---|---|---|---|
| P0-1 | 安装 git-filter-repo + gitleaks | RB | ✅ | 2026-08-28 · filter-repo 2.47.0 / gitleaks 8.30.1 |
| P0-2 | 实测子目录 CLAUDE.md 按需加载 | 新开一个 | ✅ | 2026-08-28 · 判定 A 按需加载(限定:仅文件工具触发注入,Bash 不触发) |
| P0-3 | RVH history 完整 secret scan | RB | ✅ | 2026-08-28 · 查出真密钥但定级为卫生问题(仓私有 + 服务已弃用),收尾转 backlog |
| P0-4 | 确认 Gitee 仓库体积配额 | 用户 | ✅ | 2026-08-28 · 用户确认配额足够 → T3-2 不必为体积清 blob |
| P0-5 | 决策关口 go / no-go | 用户 | ✅ | 2026-08-28 · go(全案通过,先做阶段 1) |
阶段 1 · 根 CLAUDE.md 拆分(RB 会话;独立收益,此时尚未合并)
| ID | 任务 | 会话 | 状态 | 日期 / commit |
|---|---|---|---|---|
| T1-1 | 写归属裁定表(每节 → 去处),落进 §3 | RB | ✅ | 2026-08-28 · 见 §3 T1-1 裁定结果 A/B/C/D;改判 3 处(Auth&Sync / 2-Button / Supabase 三小节留根)+ 改判 T1-4 验收数字 |
| T1-2 | 建 src-tauri/CLAUDE.md | RB | ✅ | 2026-08-28 · 21.4 KB / 5 节 / 承接 15 条红线(#1-#7 · #11) |
| T1-3 | 建 src/CLAUDE.md | RB | ✅ | 2026-08-28 · 7.0 KB / 4 节(建了,未 ⏭:导航模型 + 6 行约定索引够本) |
| T1-4 | 根 CLAUDE.md 瘦身 + 加路由段 | RB | ✅ | 2026-08-28 · 73015→50542 B(-30.8%)· 顶部加五行路由表 + Bash 不触发注入的告警 |
| T1-5 | 守门接线(check:claude-paths 传参 + ci.yml) | RB | ✅ | 2026-08-28 · 覆盖全部 5 份(含 admin/landing);顺带定死「反引号路径一律仓根相对」并修 admin 一处假阳性 |
| T1-6 | 离场检查:实测三种会话的加载行为 | 新开一个 | ✅ | 2026-08-28 · 判定 A,三场景全符合期望(结果表见 §3 T1-6「结果」)。附两条备注:Q1 探针不干净(数字泄漏在根 §2 索引行,下次换探针);admin 只验了反向。阶段 1 收工 |
阶段 2 · RVH 侧收尾(必须 RVH 会话)
| ID | 任务 | 会话 | 状态 | 日期 / commit |
|---|---|---|---|---|
| T2-1 | 在途工作收口(16 个未提交文件 + ahead 6 未推) | RVH | ✅ | 2026-08-28 · 基线已过期:这批工作已由 361dd6d / 6555c1e 等提交先行收口,验收三条全绿(status 干净 / ahead 0 / flutter test 1359 passed)。🔑 黄金向量只改了 _doc 与 _consumers 两个说明字段,expected 一个没动 → RB 两个消费方不需要同步 |
| T2-2 | 删僵尸分支 + 清 worktree | RVH | ✅ | 2026-08-28 · worktree 早已被别的会话清掉;僵尸分支 claude/brave-tereshkova-9911f3(74 behind / 0 ahead)用 git branch -d 删除成功(未合并内容为 0)。现只剩 master + origin/master |
| T2-3 | 个人文件裁定(USER_NOTES.md / PDF / QUICKSTART.md) | RVH | ✅ | 2026-08-28 · 76ec31a · 用户裁定:USER_NOTES.md + PDF git rm --cached 转本地并加 .gitignore,且要清历史 → T3-2 必须加 --path rvh/USER_NOTES.md --path 'rvh/USER_NOTES.md完整流程使用说明.pdf' --invert-paths;QUICKSTART.md 保留原地不迁 docs/。四处引用已同步改掉 |
| T2-4 | 删死历史(supabase/migrations/ · tools/vocabulary_builder/) | RVH | ✅ | 2026-08-28 · 7ffb05d · migrations 整目录删除;vocabulary_builder 改 .gitignore 忽略(磁盘上 828M 本地产物按用户裁定保留不动)。10 处活跃文档引用同步改掉。验收:flutter test 1359 passed(与删前逐字相同)·flutter analyze 唯一 error 落在已退出版本控制的 tools/vocabulary_builder/,删前就存在 |
| T2-5 | 打 pre-merge tag 作回滚锚 + 推 origin | RVH | ✅ | 2026-08-28 · tag pre-rb-merge → 7ffb05d,已推 gitee(git ls-remote --tags 复核过)。离场条件全部满足:status 0 行 · ahead/behind 0 0 · 只剩 master · 无额外 worktree |
阶段 3 · 合入(RB 会话;原子区,中途不可停)
| ID | 任务 | 会话 | 状态 | 日期 / commit |
|---|---|---|---|---|
| T3-1 | 双仓备份(tag + 本地镜像 clone) | RB | ✅ | 2026-08-28 · tag pre-rvh-merge → c10a8f9,github + gitee 双推;镜像 /tmp/rvh-mirror.git(435M,与源仓 .git 硬链等大) |
| T3-2 | 在临时克隆里 filter-repo 造前缀化历史 | RB | ✅ | 2026-08-28 · 443→438 commit(少的 5 条经逐条核实全是只改 USER_NOTES* 的 commit,filter-repo 按空提交剪枝,非误删)· 全部文件带 rvh/ 前缀 · blame 穿透 = 6(subtree 做不到的那条判据)· USER_NOTES* 与两个 .env.staging 在 --all 下均为 0 · 打包后 101M。⚠️ 本地 clone 会硬链对象导致 filter-repo 拒跑,须 git clone --no-local |
| T3-3 | merge --allow-unrelated-histories 合入 | RB | ✅ | 2026-08-28 · 509da57 · 工作树干净、blame 穿透保持 6。顺手删掉 fetch 带进来的 pre-rb-merge tag(它指向改写后的 SHA,与 RVH 仓里那个同名 tag 不是一个对象,留着会误导跨仓比对;权威锚点仍在 RVH 仓) |
| T3-4 | .gitignore / .gitattributes / .mcp.json 就位 | RB | ✅ | 2026-08-28 · 抓到一处真冲突:根 .gitignore 的 data/ 无锚定 ⇒ 连带匹配 rvh/data/(133 个已跟踪文件)。已跟踪的不受影响 ⇒ git status 一直干净 ⇒ 只有新增文件被静默忽略,最难发现的那类。修法是加 !rvh/data/ 反选(不能直接锚成 /data/ —— RB 侧 tools/vocabulary_builder_v3/data/ 的 GB 级文件正靠它挡)。另:.gitattributes 加 rvh/assets/databases/*.db binary;.mcp.json 加 rvh-debug(用绝对路径,与既有 rb-debug 一致,未采纳正文里的相对路径写法);删掉 rvh/.mcp.json(指向老仓路径的死配置,同 T3-6 删 rvh/.github/ 的理由) 🔴 2026-08-31 勘误(事后追加,原文不改):括号里「不能直接锚成 /data/」的理由不成立 —— tools/vocabulary_builder_v3/.gitignore 是逐个点名忽略那两个 GB 级输入(kaikki dump + cc_cedict)的,从不依赖根规则那条广谱匹配。代价是 !rvh/data/ 只收复了顶层那一处,而无锚定的 data/ 同时还在吞 rvh/lib/**/data/(Clean Architecture 的层名,104 个已跟踪源码)+ rvh/test/data/ + rvh/logs/,合计 110 个已跟踪文件长期处于「被 ignore」状态 —— 症状与本行描述的完全一样(已跟踪的照常工作、git status 恒干净、只有新增文件静默消失),只是范围大得多。正确修法就是锚定:2026-08-31 已把根 data/ 规则整条删除、logs/temp//backups/ 全部锚成 /logs/ 式,110 → 0,并加守卫 pnpm run check:tracked-ignored(基线 0)。详见 CHANGELOG 2026-08-31 条目与 scripts/check-tracked-ignored.sh 头注释。 |
| T3-5 | Skill 作用域(RB 四个排除 rvh/;RVH 八个变 scoped) | RB | ✅ | 2026-08-28 · arch-check / ui-check 经核对所有 rg 均硬绑 src/ src-tauri/(S28 的 docs/plans/*.md 是单层 glob,够不到 rvh/docs/plans/),只补作用域注释;code-review 增量+全量双双排除 rvh/;build-check 加 rvh 分流与 flutter analyze/test 分支;doc-sync-check 加子项目路由表。🔑 排除 rvh 不是形式主义:rvh/rvh-debug-mcp/src/*.ts 是 TypeScript,不排除会被 code-review 的 TS_FILES 按 RB 前端规则捞进去审(已实测确认)。RVH 8 个 skill 随目录进来即 scoped |
| T3-6 | 新增 ci-rvh.yml | RB | ✅ | 2026-08-28 · 4 个 job 原样搬 + defaults.run.working-directory: rvh(artifact 的 path: 属 uses: 步骤、不吃该默认值,故写 rvh/coverage/)· 删 rvh/.github/ · 照抄 ci-admin 的 artifact continue-on-error 教训。🔴 FLUTTER_VERSION 裁定为保持 3.24.0:本机 3.38.5 实测 dart format 会改写 591 个文件里的 502 个(Dart 3.7 的 "tall style" 重写),对齐 = 要么该门当场恒红、要么先提交一次 502 文件的重排版把几乎每个 .dart 的 blame 冲掉 —— 而 blame 正是本次不用 subtree 要保的东西。代价(CI 与开发机差一个大版本)已登记 §7 |
| T3-7 | check-claude-md-paths.mjs 的 ROOTS 加 rvh | RB | ✅ | 2026-08-28 · 两处都改了:ROOTS 白名单 + package.json 的 check:claude-paths 文件清单,并在脚本里留注释说明「新增子项目要动两处」。两条验收都跑了:注入 rvh/lib/nonexistent.dart 后失效路径 7→8(证明脚本真的在校验 rvh/),grep 确认 CI 那条命令里出现了 rvh/CLAUDE.md |
| T3-8 | rvh/CLAUDE.md 去镜像 + 根 CLAUDE.md 加 rvh 路由 | RB | ✅ | 2026-08-28 · 见下方「T3-8 结果」。105415 → 78365 B(-25.7%),未达正文的 <40000 B —— 原因与证据见 §7 |
| T3-9 | 离场检查:两端都能编译 + 全部守门绿 | RB | ✅ | 2026-08-28 · cargo check ✅(1 条既有 warning)· pnpm build ✅ · pnpm test:run 270 passed · flutter analyze 0 error / 0 warning(修掉 2 条合仓造成的后)· flutter test 1359 passed(与 T2-1 基线逐字相同)· 4 道守门全绿 · check-doc-links 19 = 合并前基线(修了 LINK_RE 一处合仓造成的误报)· .git 217M→317M |
阶段 4 · 兑现收益(合仓后逐项做,每项独立,可分多会话、可乱序)
| ID | 任务 | 会话 | 状态 | 日期 / commit |
|---|---|---|---|---|
| T4-1 | docs/cross-end/ 两半归并 + 编号消歧(不复原) | 任意 | ✅ | 2026-08-29 · 32 文件归并 + 2 份去重;🔴 「编号复原」改判为「不重编号 + README 消歧」(理由见下方 T4-1 正文「实际做法」);悬挂 19→10 |
| T4-2 | cross-end-check.sh 相对化 → 进 CI(最大收益) | 任意 | ✅ | 2026-08-29 · 1d671d5 · 见下方「T4-2 结果」。RVH_ROOT 默认改 $RB_ROOT/rvh · §E 重做为三方比对(仲裁者 = Supabase DDL)⇒ 3 个 ❌ 去掉 2 个、剩 1 个进 COLUMN_WAIVERS · RB 列源改静态重建(本机与 CI 同构)· 新增 --max-skip=N 堵「全 SKIP 假绿」· 新增 .github/workflows/ci-cross-end.yml(--max-skip=2)· 搭车把 check-doc-links 清到 0 并接进 ci.yml。⚠️ Linux/bash5 首跑未验(本机无 bash5/docker),期望值见「T4-2 结果」§5 |
| T4-3 | sm2-golden-vectors.json 去重(RVH fixture 改引用) | RVH 侧改 Dart,须新会话 | ✅ | 2026-08-29 · 4cf65582 · 见下方「T4-3 结果」。副本删除,Dart 直接读 ../docs/cross-end/…(cwd=rvh/,21 条断言全过)· 🔒 L1/L2 判据反转而不是废弃(L1 → 全仓负向断言「不许有第二份」;L2 → 按字面量断言 Dart 读的是根那一份),4 处反向注入全部实测变红,其中两处当场抓到我新写的守卫自己的缺陷(grep -c 配 || echo 0 吐两行;注释里的文件名喂饱计数器)· K2 改标 ✅「原因是前提没了」· 另 5 处描述同批订正 |
| T4-4 | 红线 #6d / #5i / #9 / #10 收敛为单一定义 | 任意 | ✅ | 2026-08-29 · 90e5b580 · 见下方「T4-4 结果」。四条从 4 个巨型表格单元改成「规则(两端同文)→ 为什么 → RB 落地 → RVH 落地」四段式,双向指针补齐 · 🔴 「统一编号」改判为不统一(350 处引用 + 无机械守卫)· 删掉 #10 的过期 cutover 句(T4-2 实测 F)· 顺带把「RVH 未对齐红线」的三本账收成一本(另两处停在 4 条,实际剩 2 条) |
| T4-5 | 65MB 预装库去重(symlink 实测,见 §7) | 任意 | ✅ | 2026-08-29 · c55b2e35 · 见下方「T4-5 结果」。symlink 落地(flutter build bundle 实测跟随,产物是真实 65,777,664 B db)· cross-end-check.sh §B 判据换型为结构三态(✅ symlink / ⚠️ Windows 文本占位 / ❌ 其余),四种缺陷各反向注入实测变红 · 🔴 正文的收益模型被实测推翻:git 按内容寻址,byte-equal 的两份本来就只存一个 blob(HEAD 两路径同为 b7c3bdd4;全历史并集 8 不是 13)⇒ 省的不是仓体积,是工作区 65MB + 判据结构化 · 🔴 Windows 风险用户裁定「接受 + 做成可观测」 |
| T4-6 | 删 sync-rvh-vocabulary.sh · 简化 verify-rvh-alignment.sh | 任意 | ✅ | 2026-08-29 · 0915de31 · 见下方「T4-6 结果」。🔴 两项都改判:① sync-rvh-vocabulary.sh 不删、改型 —— git mv 成 scripts/check-vocab-asset.sh(cp 随 symlink 消失,留下体积闸 + 卫生断言这两样没有替代品的东西),并接进 ci-cross-end.yml 换一条真必经之路;② verify-rvh-alignment.sh 一行没改 —— 实测它零跨仓断言,「同仓后很多断言变恒真」的前提不成立 |
| T4-7 | 删 CLAUDE.md §9「跨仓库路径约定」整节 | 任意 | ✅ | 2026-08-29 · c7ae7eb8 · 见下方「T4-7 结果」。整节已删 + §9 其余 6 小节改写为「双端整合」· 🔴 顺带查出 §9 在说三处假话(RVH 位置 / 「RVH 待镜像」停了七周 / 「跨仓路径检查不到」已作废)· 两份改名计划标 ✅ 归档 + 5 处指针 · 会话隔离规则收窄为「按工具链判,不按目录判」· ⚠️ 净增 3.4KB(删的只有 ~0.7KB,其余是订正) |
| T4-8 | 合并 docs/product.md(修 RVH 那份过期定位) | 任意 | ✅ | 2026-08-29 · 见下方「T4-8 结果」。RVH 那份收缩成指针(不删:8 个文件在引用它)· 🔴 它不只是过期,是在说三条与当前产品相反的话(其中「❌ 云端同步」最重 —— 跨端同步是这产品的骨架)· ⚠️ 同批查出根侧自己也有两处假话(预装词数 / 「5 表同步」),说明别只审对端那半边 |
| T4-9 | 老 gitee RVH 仓归档(只停推,不删) | 用户 | ✅ | 2026-08-29 · 用户已在 gitee 置为「关闭」(该状态继承「暂停」的代码只读、并冻结新建任务/评论/改名;⚠️ 因此改名要在置关闭之前做)。仓未删 —— tag pre-rb-merge 与回滚源完好。归档前预扫描见下方结果段。归档前的机械前置全做完:修掉 20 处会在归档后误导的引用(3 条可复制的部署命令 · admin/CLAUDE.md 仓库地图说假话 · pipeline 6 处指向老仓的计划文档 · backlog 两份开场模板还在教一条 T4-7 已删的规则 · 老仓 .mcp.json 配置指引,那文件 T3-4 就删了)。🔴 正文给的扫描范围与命令都不够(范围漏掉真问题所在的 5 个目录;裸模式会把 Flutter 包名 package:reading_vocab_helper/ 全捞进来 —— 首扫 200+ 文件几乎全是噪声)。复扫后残留全是历史记述 ✅;回滚源完好(老仓 tag pre-rb-merge 在场、本仓两个 remote 都不指它) |
| T4-10 | 🔴 修 ci-rvh.yml 恒红(合仓遗留,非 T4 原有项) | 须新会话(要跑 Flutter) | ✅ | 2026-08-29 · f249d615 + 90c9e8ac + 8feade41(+ 4cf65582 补齐 T4-3 被误带走的那半)· 🎉 首绿 = run 33225931007,四个 job 全 success,flutter test 1359 passed / 5 skipped 与本机基线逐字相同。见下方「T4-10 结果」。🔴 实际死因是 6 个不是 2 个(后两个要推上 CI 才看得见):原记的 ① opencv_dart / ② 架构违规之外,还有 ③ .env 是声明过的 asset 但不进 git ⇒ flutter analyze 报 asset_does_not_exist、④ 架构 job 后 4 条 grep 从未执行过(第 1 条一失败就 exit),首次跑起来又抓到 8 处;⑤ FLUTTER_VERSION=3.24.0 解析不了本项目依赖 —— 证伪了 T3-6 钉版本的前提,已改判为对齐 3.38.5 + 删格式门(用户裁定);⑥ vendored 的 libsqlite3 只有 arm64-android + x64-macos,CI 的 ubuntu 要 x64-linux ⇒ Unit Tests job 一条测试都跑不了 |
2. 阶段 0 · 预检与准备
阶段不变量:全程只读 + 装工具,不改任何一个仓的内容。任何一项失败都可以原地停下,零代价。 离场条件:P0-5 给出 go/no-go。no-go 则本计划到此为止,
rvh-merge-evaluation.md的结论作废并注明原因。
P0-1 安装工具链
会话:RB · 前置:无
bash
brew install git-filter-repo gitleaks验收:
bash
git filter-repo --version && gitleaks version注意:bfg 不需要(filter-repo 覆盖其全部用途且更安全)。 本机已确认 flutter 3.38.5 / dart 3.10.4 可用。
顺手发现(不属本任务,登记备查):RVH 的
.github/workflows/ci.yml把FLUTTER_VERSION钉在3.24.0,与本机 3.38.5 差了一个大版本。 搬 CI 时(T3-6)要么对齐、要么显式说明为什么钉旧版。
P0-2 实测子目录 CLAUDE.md 按需加载
会话:新开一个(不能在本会话做——上下文已被污染)· 前置:无
这是整个方案的地基。评估文档 §3 的全部算术都建立在 「根 CLAUDE.md 恒加载、子目录的碰到才叠加」上。不通则本计划作废。
动作:新开一个 RB 会话,只做一件事——读 admin/lib/queries.ts 的前 20 行, 然后检查 admin/CLAUDE.md 的内容是会话启动时就在上下文里,还是读了 admin 文件之后才注入。
验收判据(三选一,写进台账备注):
- ✅ 按需:启动时答不出探针问题,触达 admin 文件后答得出 → 方案成立,继续。
- ❌ 恒加载:启动时就答得出 → 拆分不省 token,方案收益模型作废,回 §8 看退路。
- ⚠️ 不确定:按 ❌ 处理(不赌)。
探针(已核验唯一性:三份 CLAUDE.md 里只有对应那份含此串):
| 探针 | 只出现在 | 标准答案 |
|---|---|---|
| admin 技术红线 第 1 条 | admin/CLAUDE.md §3 | 「service_role key 绝不暴露给浏览器」 |
| 小标题「跨项目决策参考」 | admin/CLAUDE.md §2 | — |
| 小标题「3.2 访问统计」 | landing/CLAUDE.md | — |
交接 prompt(整段复制给新会话)
text
这是一次只读实测,别改任何文件,别用 Agent/子会话。按顺序做四步,每步都要如实回答。
【第 1 步|先别看任何文件】
不要读取、grep、cat 任何文件。仅凭你此刻上下文里已有的内容回答:
Q1. admin/CLAUDE.md 的「§3 技术红线」第 1 条写的是什么?
Q2. admin/CLAUDE.md 里有没有一个叫「跨项目决策参考」的小标题?
Q3. landing/CLAUDE.md 里「3.2 访问统计」讲的是什么?
如果上下文里没有这些内容,就明确回答「上下文里没有 admin/CLAUDE.md,答不出」。
不许猜、不许推理、不许"根据常见做法"作答 —— 这一步的价值全在于诚实地报告你能不能看见。
同时报告:你此刻的上下文里出现了哪几份 CLAUDE.md?(根 / admin / landing / 其他)
【第 2 步|触达 admin 文件】
读 admin/lib/queries.ts 的前 20 行。只读这一个文件,不要读 admin/CLAUDE.md 本身。
【第 3 步|再答一次】
重新回答 Q1、Q2。并报告:现在上下文里出现了哪几份 CLAUDE.md?
【第 4 步|结论】
判定属于以下哪一种,一句话说清:
A. 按需加载 —— 第 1 步答不出,第 3 步答得出
B. 恒加载 —— 第 1 步就答得出
C. 不确定 —— 其他情况
然后把结论回写到 docs/plans/rvh-merge-plan.md 的 §1 台账 P0-2 那一行
(状态 ✅/❌ + 日期 + 判定 A/B/C),并同步顶部状态行。提交用:
git commit docs/plans/rvh-merge-plan.md -m "docs(plans): P0-2 实测 —— 子目录 CLAUDE.md <判定>"
背景(读完就够,不必翻别的):本仓正在评估把 RVH(Flutter 移动端)并入 rvh/ 子目录。
根 CLAUDE.md 每会话恒加载(约 20.7k tokens),子目录的按需叠加——整个合并方案的收益
全建立在后半句上。这一步就是验证后半句。判定 B 则方案作废,见该计划 §8 退路。⚠️ 为什么必须新开会话:任何已经读过 admin/ 下文件、或读过本计划的会话, 上下文都已被污染,第 1 步必然"答得出",测出来的是假的 B。
结果(2026-08-28 实测,判定 A · 按需加载)
会话启动时上下文里只有两份 CLAUDE.md:全局 ~/.claude/CLAUDE.md + 根 ~/reading-browser/CLAUDE.md。 三个探针全部答不出(landing/CLAUDE.md 的「3.2 访问统计」自始至终没进来)。 触达 admin/lib/queries.ts 后 admin/CLAUDE.md 全文注入,两个 admin 探针立刻答得出。 附带现象:admin 的三个 scoped skill(deploy / admin:code-review / admin:doc-sync-check) 也是同一刻才注册进工具集——skill 与 CLAUDE.md 同批按需加载,不是只有文档。
⚠️ 限定条件(必须带进阶段 1 的设计里):注入挂在文件工具上,Bash 不触发。
(原文这里用的是红线锁标记,已改为 ⚠️ —— 本文件 header 承诺不含锁标记, 那会让
/arch-checkS28 的 4 份基线变 5 份、平白多一次人工裁定。约束本身一个字没动。) 实测先用head -20 admin/lib/queries.ts读完整个文件内容,什么都没被注入; 换成 Read 工具读同一个文件才触发——且那次 Read 返回的是Wasted call — file unchanged(正文根本没重新给),注入照样发生。判据是「文件工具解析到了那个路径」,与是否真读到内容无关。
含义:一个全程用 cat / grep / sed 干活的会话(某些 harness 模式会默认走 Bash) 拿不到 rvh/CLAUDE.md——它会在没有 RVH 控制文件的情况下改 RVH 代码。 收益模型(子目录那份不计入每会话基线)成立,但「改 rvh/ 时一定读得到 rvh/CLAUDE.md」 不是无条件成立的。阶段 1 拆分时不要把任何红线级约束只放进 rvh/CLAUDE.md 就当数—— 按 CLAUDE.md §4「红线归属规则」,跨端不变量仍需在根侧留指针。
P0-3 RVH history 完整 secret scan
会话:RB(只读对端)· 前置:P0-1
合仓 = 把 RVH history 推上 GitHub。
⚠️ 本段原先写着"抽查过,都是
your_g…占位模板"——那个结论是错的,已被下面的实跑推翻。 抽查只看了.env.production与.env.development,恰好漏掉了唯一带真值的.env.staging。 保留这句话是为了让后来的会话看见这个形状:同族文件里"抽两份都干净"证明不了第三份干净。
bash
# ⚠️ gitleaks 8.30 已删掉 `detect` 子命令,是 `gitleaks git <path>`
gitleaks git ~/reading_vocab_helper --log-opts="--all" \
--report-format json --report-path ./rvh-gitleaks.json --redact=60 --no-banner --exit-code 0--redact=60 保留前 40% 字符——足以分辨 your_xxx 占位符与真密钥,又不会把完整密钥 落进日志/上下文。全 100% 遮蔽反而没法做这个判断。
验收:0 finding,或每条 finding 都被人工判定为占位符/误报并记进台账备注。 有真密钥则本任务变成阻塞项:先轮换该密钥,再决定是 filter-repo 清史还是放弃保留历史。
🔴 P0-3 结果(2026-08-28 实跑)
RVH:8 条 finding,其中 4 条是真密钥。
| # | 判定 | 内容 |
|---|---|---|
| 1-4 | 🔴 真密钥 | BAIDU_OCR_API_KEY + BAIDU_OCR_SECRET_KEY,熵 4.42,落在 .env.staging 与 assets/.env.staging,引入于 e3e236dd(2026-01-02,提交信息是「docs: 文档体系重构与优化」) |
| 5-8 | ✅ 误报 | YOUR_PROJECT_REF / YOUR_LOCAL_ANON_KEY 占位符,在两份 curl 示例文档里 |
为什么之前的抽查没看出来(教训,值得记住):写评估文档时抽查了 .env.production 与 .env.development,两份都是 your_g… 占位模板,于是给了乐观结论。 真值只在 .env.staging 这一份里 —— 三份同族文件,两份是模板一份是真的。 这正是"抽查不是审计"的实例。
当前暴露面:
- 两个文件已从 HEAD 移除(
bb5dc7f/60ac1e1),但留在 history 里。 - 凭据已不在使用,但不等于已失效(2026-08-28 复核后订正): 代码里 Baidu OCR 引擎还在(
lib/features/ocr/data/engines/baidu_ocr_engine.dart等四处), 但lib/config/app_config.dart:166读的是dotenv.env['BAIDU_OCR_API_KEY'], 而当前.env里根本没有这两个键,.env.example里也是注释掉的。ocr_engine_providers.dart:36的 provider 因此返回null,引擎优先级Google Vision > Baidu OCR > ML Kit直接跳过它。 ⇒ 这是一对已废弃但很可能仍然有效的凭据。百度的 AK/SK 不会自己过期, 只要控制台里那个「应用」还在,它就还能认证、还能被计费。 风险不在 RVH 用不用它,在百度账号那边它还认不认。 .env.example里写的是your-api-key-here占位符 —— 当前的配置姿势是对的,脏的只有历史。- gitee 仓可见性未能从命令行判定:禁用凭据助手后 Gitee 对公开仓/私有仓/不存在的仓 一律要求认证,
ls-remote区分不出来(先前那次"未认证成功"是 macOS keychain 助手在背后供了凭据)。 网页未认证访问返回 403。需要用户在 Gitee 仓库设置里确认——这决定严重性: 私有 = 卫生问题;公开 = 已暴露 8 个月的事故。
处置:已定级为卫生问题,不阻塞(2026-08-28 用户确认后裁定)
两项事实把严重性从「事故」降到「卫生问题」:
- gitee
ttfishnet/reading_vocab_helper是私有仓 → 从未对外暴露。 - 百度 OCR 已弃用 → 删应用不需要任何替代品,RVH 侧零改动。
⇒ 收尾转 backlog.md 的「百度 OCR 弃用收尾」条目,本计划不再为它阻塞。 那里记了三件事:① 控制台删应用(用户)· ② 清历史(见下)· ③ 删 RVH 里的死代码(可选、独立)。
本计划只承担 ②,且已裁定「清」:T3-2 跑 filter-repo 时加两个 --path ... --invert-paths 参数即可(那一步本来就要在临时克隆里跑 filter-repo,边际成本 ≈ 0)。
⚠️ 顺序不能反:backlog ① 要在 ② 之前。只清史不删应用等于什么都没做 ——旧克隆、gitee 上的已有副本、任何 fork 都还留着那对值。
原先列的两个选项(已裁定为「清」,留档)
- 清史:
git filter-repo时加--path .env.staging --path assets/.env.staging --invert-paths(T3-2 本来就在临时克隆里跑 filter-repo,加两个参数的边际成本 ≈ 0)。 - 不清:作废后旧值无效,且合并目标是 GitHub 私有仓。可接受。
RB 自身顺带也扫了(合仓前该知道):10 条 finding,0 条新问题。
| # | 判定 | 内容 |
|---|---|---|
| 1-2 | ✅ 测试夹具 | ci-admin.yml 与 admin/tests/unit/sentry-scrub.spec.ts 里的 JWT,解出 role=anon 且无 ref = 假 token |
| 3-4 | ✅ 测试夹具 | sb_secret_AbCdEf123456 / sb_publishable_AbCdEf123456 —— 验证 Sentry 脱敏器会遮蔽它们的测试 |
| 5-6 | ✅ 误报 | YOUR_PROJECT_REF 占位符 |
| 7-8 | ⚠️ 已知已接受 | supabase/sql/cron-setup.sql 的 Supabase key,解出 role=anon(不是 service_role)。CLAUDE.md §5 明写 anon_key「公开 by design,RLS 保数据」 |
| 9-10 | ⚠️ 已知已接受 | src-tauri/assets/sql/init_data.sql · commands/init.rs 的旧 anon key 字面量。HEAD 里已经没有了(2026-07-23 改构建期注入),只在历史里 —— 与 CLAUDE.md §5「旧字面量仍在 git 历史 + 已发版二进制,真正作废需轮换 anon key(见 backlog)」完全一致 |
全仓 0 处 service_role。 RB 侧不构成合仓阻塞。
P0-4 Gitee 体积配额
会话:用户 · 前置:无
.git 合并后约 650M(RB 214M + RVH 442M)。GitHub 侧 ttfishnet/lampio 是私有仓、 diskUsage 约 75MB,压力小;Gitee 是双推备份之一(CLAUDE.md §8 分支卫生),需确认配额。
结果影响 T3-2:若卡上限,filter-repo 那一步顺带清掉 RVH 历史里 31 个 dict blob(省 200M+)。 这一步本来就在临时克隆里跑,加个 --path 过滤几乎零成本,所以配额紧张不构成阻塞。
P0-5 决策关口
会话:用户
前四项结果摆在一起,明确 go / no-go,写进台账。go 之后再动阶段 1。
3. 阶段 1 · 根 CLAUDE.md 拆分
阶段不变量:这一阶段与合并无关,独立成立。做完即使不合并也已受益(根文件从 ~20.7k 降到 ~6-7k tokens)。 离场条件:T1-6 实测三种会话加载正常,且 CI 全绿。此时可以无限期停在这里。 并行禁令:根
CLAUDE.md是所有会话的地图。这一阶段独占工作树, 或按 CLAUDE.md §8 开Agent(isolation:"worktree")。 P0-2 的限定在这里第一次产生后果:子目录 CLAUDE.md 的注入挂在文件工具上, 全程走 Bash(cat/grep/sed)的会话拿不到它。所以下沉的判据不能只看"谁会读", 还要看"读不到会怎样"——见 T1-1 的裁定原则第 5 条。
交接 prompt(开新会话时整段复制)
text
接手 RVH 合仓计划的阶段 1(拆分根 CLAUDE.md)。
先做三件事,再动手:
1. 读 docs/plans/rvh-merge-plan.md 的 §0 冷启动开工指引 + §1 进度台账 + §3 阶段 1 全文。
那份文件是本任务的唯一真相源,每个任务都写了「会话/前置/动作/验收/回滚」,
不需要回看任何历史对话。
2. 跑 `git status --short` 与 `git worktree list` 确认工作树干净、没有别的会话在跑。
本阶段要改根 CLAUDE.md(枢纽文件),不干净就先停下问用户。
3. 台账里 P0-5(决策关口)还是 ⬜。向用户确认一句 go/no-go,回写台账再开工。
然后按 T1-1 → T1-6 顺序做。三条容易踩的:
- T1-1 是裁定不是搬运,**先把归属表写进文档再动手**,不许边搬边想。
- 搬运前存一份小标题对照:`grep -nE '^#{2,4} ' CLAUDE.md > /tmp/claude-md-toc-before.txt`,
搬完逐条勾掉,确认一条没丢。
- 每个任务做完先跑验收命令,再回写 §1 台账,再提交。
提交用 pathspec 形式绕过共享 index:`git commit <具体文件> -m "..."`。
本阶段边界:**只做阶段 1**。阶段 2 必须是 RVH 会话,阶段 3 是原子区,都不要在这次会话里开。
阶段 1 做完(T1-6 离场检查全绿)就停,可以无限期停在那里——它独立成立,不合并也已受益。T1-1 归属裁定表
会话:RB · 前置:P0-5 = go
这是整个阶段最重要的一步,不许边搬边想。 先逐节裁定去处,把表落进本文件本小节, 再动手。裁定原则:
- 双端契约 → 留根(合仓后它们第一次拥有唯一物理归属,正是收益所在)
- 桌面端专属 →
src-tauri/CLAUDE.md - 前端 UI 专属 →
src/CLAUDE.md(或仅留指针到docs/ui-standards.md) - 仓库地图 / git 纪律 / 安全 / 文件组织 / skill 索引 → 留根
- 第 5 条(P0-2 实测后新增):判据不只是「谁会读」,还有「读不到会怎样」。 子目录 CLAUDE.md 的注入挂在文件工具上——全程用
cat/grep/sed干活的会话 (某些 harness 模式默认走 Bash)根本拿不到它。 所以:违反后果够得上"数据错/泄漏/断链"的约束,必须在根侧至少留一条指针, 哪怕正文下沉。反之,纯粹是"怎么写更顺手"的约定(命名、目录、组件选型) 下沉后读不到也只是风格漂移,可以完全交给子文件。
当前分节体量(2026-08-28 实测,复现命令见 rvh-merge-evaluation.md 附录 A):
| 节 | ≈tokens | 建议去处 |
|---|---|---|
| §1 项目概览 | 393 | 根 |
| §2 项目结构(仓库地图 + 非显然约定索引 + 导航模型 + commands 模块) | 3599 | 拆:仓库地图留根;「非显然约定索引」「顶层导航模型」「Rust Commands 模块」下沉 |
| §3 架构要点 | 1592 | src-tauri/CLAUDE.md(multi-webview / IPC / DB 双通道 / 三条设计原则) |
| §4 技术红线 | 5132 | 拆:#5i / #6d / #9 / #10 留根(双端契约);#1-#8 / #11 下沉 |
| §5 数据库 | 1758 | src-tauri/CLAUDE.md |
| §6 安全规则 | 215 | 根 |
| §7 项目 Skills | 1696 | 根(但可压缩:12 行表格 → 一句话 + 指针) |
| §8 开发工作流 | 1930 | 根(git 纪律 / 并行会话纪律是全仓级) |
| §9 双端整合 | 2449 | 根(合仓后 T4-7 会删掉「跨仓库路径约定」一整节) |
| §10 当前状态 | 615 | 根 |
| §11 环境信息 | 65 | 根 |
| §12 文件组织 | 885 | 根 |
⚠️ §4 不能整块下沉。 #5i(push user-scoping,对端 #5h)、#6d(墓碑 synced_at, 明写"双端契约 RB ⇄ RVH 同文")、#9(lemmatizer normalize)、#10(预装库 byte-equal) 四条是双端契约,正是合仓后要收敛成单一定义的东西。下沉进 src-tauri/ 等于把它们 藏进只有改 Rust 时才可见的地方,而改 Dart 的会话再也看不到——比现状更糟。
验收:裁定表写进本小节且每一节都有明确去处(不许留"待定")。零代码改动。
裁定结果(2026-08-28 落定,T1-1 ✅)
先量化。全文 73015 B / 54 个小标题,逐节字节数(复现:见本小节末尾脚本)。 下表的「去处」列一旦写定,T1-2/T1-3/T1-4 只做机械搬运,不再重新裁定。
产物三份:根 CLAUDE.md(瘦身)· src-tauri/CLAUDE.md(新建)· src/CLAUDE.md(新建)。
A. 逐节归属
节(### 级) | 字节 | 去处 | 依据 |
|---|---|---|---|
| 目录 | 459 | 根(重写,随新结构) | — |
| §1 产品定位 | 509 | 根 | 原则「项目身份」 |
| §1 技术栈 | 835 | 拆:框架/前端/样式/状态/后端/同步 6 行留根;UI 库 + 业务 molecule 两行 → src/ | 后两行是前端选型细节,读不到只是风格漂移(原则 5 反面) |
| §2 目录布局 | 1443 | 根,与「仓库地图」合并(两处画同一棵树,是第二本账) | 原则「仓库地图留根」 |
| §2 仓库地图 | 3797 | 根(吸收上一行后压缩) | 同上 |
| §2 非显然约定索引 | 4479 | 拆:snapshot.rs/cache_protocol.rs/vocab_scope.rs/content-script/* 4 行 → src-tauri/;commands.ts/TextDraftEditor/epubStart/url.ts/moduleNav/revisit 6 行 → src/;表头那段「加新行前先自问」的元规则两份子文件各写一次(它是写作纪律,不是事实,复述不构成第二本账) | 原则 2/3 |
| §2 顶层导航模型 | 1444 | src/ | React Workspace 模块,纯前端 |
| §2 Rust Commands 模块 | 1536 | src-tauri/ | 原则 2 |
| §3 Multi-Webview 架构 | 1082 | src-tauri/ | webview 是 Tauri 概念,建窗在 lib.rs |
| §3 DB 双通道 | 184 | src-tauri/ | 原则 2 |
| §3 Content-Script IPC | 167 | src-tauri/ | content-script 源在 src-tauri/src/content-script/ |
| §3 Auth & Sync | 2640 | 根 ⚠️ 改判(§3 建议列原写 src-tauri/) | 同步矩阵 / 墓碑传播覆盖 / merge 策略 / cloze 两条 🔒 全是 RB⇄RVH 共写语义(cross-end/23 明写放开 RVH 采集路径)。下沉 = 改 Dart 的会话永远看不见 → 原则 1 |
| §3 2-Button 复习系统 | 335 | 根 ⚠️ 改判 | 正文自称「三端行为契约」→ 原则 1 |
| §3 架构设计原则(3 条) | 1123 | src-tauri/,src/ 留一行指针 | 讲 Tab/webview/跨边界通信,落点在 Tauri 边界 |
| §4 红线归属规则 | 1828 | 根(压缩 admin force-dynamic 那段举例为一句) | 它是治理元规则,且本次拆分后多了两个归属去处,必须留根 |
| §4 红线表 20 条 | 15459 | 拆,见下表 B | — |
| §5 迁移版本 | 3399 | src-tauri/ | RB 本地迁移链,RVH 有自己的一套 |
| §5 预装数据 | 2145 | src-tauri/ | VOCABULARY_SEED_VERSION + build.rs 注入,全是 Rust 侧 |
| §5 Supabase 同步表 | 760 | 根 | 双端 + admin 共享契约中枢 → 原则 1 |
| §5 Supabase vocabulary 表角色 | 583 | 根 | 缓冲池流程是 RB/RVH/admin 三方的 → 原则 1 |
| §5 Soft-Delete 策略 | 271 | 根 | 双端 push/pull 语义 → 原则 1 |
| §6 安全规则(两小节) | 748 | 根 | 原则「安全留根」 |
| §7 Skill 一览 | 4303 | 根,重压缩至 ~900 | 「触发条件」列是 skill description 的副本,而 description 每会话自动注入(实测)→ 整列是第二本账。只保留 description 里没有的:深度、动作类/流程链之别 |
| §7 Skill 协作链 | 1442 | 根,与 §8「标准开发流程」合并(两处写同一条链) | 去重 |
| §8 标准开发流程 | 472 | 并入上一行 | 去重 |
| §8 Plan Mode 计划持久化 | 545 | 根 | 全仓级 |
| §8 代码提交规范 | 382 | 根 | 全仓级 |
| §8 分支管理 & 版本发布 | 4832 | 根,指针化压缩 | 正文自己写着「完整版见 docs/coding-standards.md §10,本节为速查摘要」——而摘要有 4.8KB。压掉展开论证,三条静默丢失路径 A/B/C + 隔离决策规则 + 发版 = tag 一字不删(后果 = 静默丢代码,原则 5 最高档) |
| §8 数据库变更流程 | 262 | 根 | 触发 Schema 同步协议,双端 |
| §8 缺陷修复原则 | 151 | 根 | 全仓级 |
| §9 双端整合(全 9 小节) | 8609 | 根 | 整节即双端契约本体 → 原则 1。不压缩(T4-7 合仓后才删「跨仓库路径约定」) |
| §10 当前状态(全 4 小节) | 1684 | 根 | 全仓级指针页 |
| §11 环境信息 | 224 | 根 | 太短,拆不划算 |
| §12 文件组织 & 代码规范 | 3119 | 根 | 「新文件放哪」是建文件那一刻要用的,任何会话都可能建文件;且它已是纯指针页 |
B. §4 红线表 20 条逐条归属
判据(机械可复核):红线正文里明确出现 RVH / 对端 / 双端 / 三端 / byte-equal 字样的 → 留根。 这与 §3 建议列的「#5i / #6d / #9 / #10」完全吻合,另外 #6a 的 🔴 跨端并发脚注属提及不属契约,随正文下沉。
| 红线 | 字节 | 去处 | 判据命中 |
|---|---|---|---|
| #5i push dirty-check 按归属过滤 | 1705 | 根 | 「RVH 对应红线 #5h」 |
#6d 墓碑 synced_at 四条子规则 | 3226 | 根 | 「双端契约(RB ⇄ RVH 同文)」 |
#9 lemmatizer::normalize() | 541 | 根 | 「跨端共享」「两端各一份的对齐义务」 |
| #10 预装库 byte-equal | 2427 | 根 | 「RB … 与 RVH … 字节相等」 |
| #1 rusqlite 锁 v0.31 | 78 | src-tauri/ | — |
#2 禁 LOWER() 用 COLLATE NOCASE | 80 | src-tauri/ | — |
#3 content-script 用 __TAURI_INTERNALS__ | 107 | src-tauri/ | — |
| #4 DOM 操作前断开 MutationObserver | 101 | src-tauri/ | — |
#5 pull 用 server_updated_at=gt. | 135 | src-tauri/ | — |
#5a last_sync_at 仅 errors.is_empty() 推进 | 174 | src-tauri/ | — |
#5b pull_* INSERT 必须写 user_id | 254 | src-tauri/ | — |
#5c 登出必须 resetAllTabs() | 235 | src-tauri/ | — |
| #5d watermark = 实际返回行的 MAX | 710 | src-tauri/ | — |
#6 软删用 deleted_at | 103 | src-tauri/ | — |
| #6a 父行删除显式软删子树 | 2022 | src-tauri/ | — |
#6b push payload deleted_at 传真值 | 327 | src-tauri/ | — |
| #6c pull 父行守卫三态 | 1430 | src-tauri/ | — |
#7 save_word 复活软删条目 | 76 | src-tauri/ | — |
| #11 已出厂迁移一个字节不能改 | 1642 | src-tauri/ | — |
| #8 组件内禁 hex 字面量 | 66 | src/ | — |
C. 原则 5 的落地形状:根侧留全量红线索引,不留摘要
P0-2 实测的限定(注入挂文件工具,cat/grep 不触发)意味着:一个全程走 Bash 的会话 改 src-tauri/ 也可能读不到 src-tauri/CLAUDE.md。原则 5 要求高后果约束在根侧留指针。
落地不做「挑几条高后果的写摘要」——那是可漂移的第二本账。改为: 根 §4 保留一张 20 行的全量索引表(编号 | 一句话标题 | 正文所在),四条双端契约的正文接在索引后面。
好处:① 根侧永远看得见「一共有哪些红线、哪条在哪」;② 索引行不复述理由,不构成第二本账; ③ 新增红线时索引与正文必须同时动,漂移立刻可见。索引本身约 1.0 KB。
D. 体量落点(实测收口,2026-08-28 T1-4 完成后回填)
估两次,错两次,都偏低。 记录下来,因为错的方式有规律:
| 根 CLAUDE.md 字节 | |
|---|---|
| 原计划 §3 建议列的目标 | < 25 000 |
| T1-1 裁定时的重估(本节初版) | ~37 000 |
| T1-4 实测 | 50 542 |
拆分前 73 015 B → 拆分后 50 542 B,降 30.8%(≈20.7k → ≈14.3k tokens,每会话省约 6.4k)。 src-tauri/CLAUDE.md 21 622 B · src/CLAUDE.md 7 250 B,两者按需叠加。
估低的根因(两次同一个):我按「哪些能下沉」清点,从没逐节称过「留根块自己有多重」。 最后一次估算里,我把 8 个留根小节(§1 / §5 / §6 / §7 / §10 / §11 / §12 / 头部 + 目录) 打包估成 ~7 500 B,实测 13 973 B —— 单这一笔就差了 6.5 KB。 另外两笔是我在实施时主动放弃了两处压缩(见下),各差 ~1.8 / ~2.9 KB。
实测分布:
| 节 | 字节 | 备注 |
|---|---|---|
| §4 技术红线 | 12 928 | 四条双端契约正文 7 899 + 全量索引 ~1 900 + 归属规则 |
| §9 双端整合 | 8 629 | 裁定即「不压缩」,T4-7 合仓后才删「跨仓库路径约定」 |
| §8 开发工作流 | 6 396 | 已指针化 10.1/10.2/10.5 |
| §2 项目结构 | 5 839 | 两张树图未合并,见下 |
| §3 架构要点 | 3 315 | Auth&Sync + 2-Button |
| §12 文件组织 | 3 119 | |
| §7 Skills | 2 304 | 4 303 → 压缩后 |
| §10 / §5 / 头部 / §1 / §6 / §目录 / §11 | 8 052 |
实施中对 A 表的两处偏离(先记账,不藏):
- §2「目录布局」与「仓库地图」没有合并。 A 表写的是「合并(两处画同一棵树,是第二本账)」。 动手时判断这个框定不成立:前者标注每个目录是干什么的(含
pages/与content-script.js两个 ⚠️ 陷阱),后者标注本体 vs 卫星与耦合方向。合并必然牺牲其中一个框定, 而仓库地图那段自己就写着「这解释了目录树的'不对称'——它是结构事实,非命名问题」。 代价 ~1.8 KB,值。 - §8「分支管理」只做了一半压缩。 已确认
docs/coding-standards.md§10(16 366 B) 完整覆盖 10.1-10.5,故把「何时在 main / 何时切分支」「分支卫生命令」指针化; 但多会话并行纪律整段与「发版 = tag」全部保留原文——路径 A/B/C 的后果是静默丢代码, 而coding-standards.md不是每会话加载,正是裁定原则 5 说的那种「读不到会怎样」。
验收闸门改判:退役字节数,改用结构判据。
原因:字节目标与裁定原则 1(双端契约留根)在这份文件上是互相矛盾的约束—— 留根块的不可压缩质量就是 ~50 KB,凑到 25 KB 只能靠删真实约束。 一个每次都要往上调的数字闸门,等于没有闸门。
T1-4 实际验收(三条,全部已通过):
node scripts/check-claude-md-paths.mjs CLAUDE.md src-tauri/CLAUDE.md src/CLAUDE.md退出码 0 ✅- B 表 16 条下沉红线在根 §4 全量索引里各有一行(索引 20 行 = 4 条留根 + 16 条下沉)✅ —— 这是防「为凑数字删约束」的真闸门
- 行级无丢失核对:
git show HEAD:CLAUDE.md的每个实质行(≥25 字符)都能在新三文件中找到, 例外仅限「裁定内的重写/压缩」与../路径改写造成的假阳性,逐条过目 ✅ (据此补回了三处真丢失:非显然索引前言的考古依据 ×2、/vocab-reseed的「收到 RVH 交接文档」触发、 动作类 skill 的深度标注) node scripts/check-doc-links.mjs悬挂引用 19 处,与拆分前逐条相同,新文件零新增 ✅
复现命令
bash
python3 - <<'EOF'
import re
lines=open('CLAUDE.md',encoding='utf-8').read().split('\n')
heads=[(i,l) for i,l in enumerate(lines) if re.match(r'^#{2,3} ',l)]+[(len(lines),'EOF')]
for k in range(len(heads)-1):
i,j=heads[k][0],heads[k+1][0]
print(f"{sum(len(x.encode())+1 for x in lines[i:j]):7d}B {heads[k][1][:70]}")
EOFT1-2 建 src-tauri/CLAUDE.md
会话:RB · 前置:T1-1
按裁定表搬运。逐条核对,不许丢:搬之前先把根 CLAUDE.md 的小节标题清单存成对照 (grep -nE '^#{2,4} ' CLAUDE.md > /tmp/claude-md-toc-before.txt),搬完逐条勾掉。
验收:
bash
node scripts/check-claude-md-paths.mjs src-tauri/CLAUDE.mdT1-3 建 src/CLAUDE.md
会话:RB · 前置:T1-1
体量小(~2k)。如果裁定下来内容太少,可以不建,把前端约定留在根或纯指向 docs/ui-standards.md / docs/coding-standards.md。⏭ 掉也是合法结果,写进台账即可。
T1-4 根 CLAUDE.md 瘦身 + 路由段
会话:RB · 前置:T1-2(+ T1-3 若做)
删掉已下沉的内容,并在文件顶部扩写现有那段 admin/landing 路由提示,加上 「只碰 src-tauri/ 就直读 src-tauri/CLAUDE.md」这类指引。
验收:
bash
wc -c CLAUDE.md # 实测 50542 B(-30.8%);字节闸门已退役,见 T1-1 §D
node scripts/check-claude-md-paths.mjs
diff <(grep -oE '^#{2,4} .*' /tmp/claude-md-toc-before.txt) /dev/null | head # 人工核对无遗漏T1-5 守门接线
会话:RB · 前置:T1-4
scripts/check-claude-md-paths.mjs 默认只查根 CLAUDE.md(它接受额外文件作参数)。 新建的子文件不接线 = 它们的失效路径无人守。
改两处:
package.json的check:claude-paths加上新文件参数.github/workflows/ci.yml(约第 81 行那一步)随之生效
验收:pnpm run check:claude-paths 覆盖到全部 CLAUDE.md 文件且退出码 0。
顺手确认:
scripts/check-doc-links.mjs不在 CI 里(ci.yml只跑 no-inline-zh / no-inline-en / schema-frozen / version-sync / audit:locale / claude-paths / test / build)。 这解释了为什么 19 处悬挂引用能长期存在。是否补进 CI 不属本计划, 但 T4-1 做完 cross-end 归并后是个自然时机。
T1-6 离场检查
会话:新开一个 · 前置:T1-5
开三个新会话(或一个会话分三次触达),确认:
| 场景 | 期望 |
|---|---|
只读 src-tauri/src/lib.rs | 根 + src-tauri/CLAUDE.md 加载,admin/ 那份不加载 |
只读 admin/lib/queries.ts | 根 + admin/CLAUDE.md,src-tauri/ 那份不加载 |
只读 docs/product.md | 只有根 |
全绿则阶段 1 收工,可以无限期停在这里。
交接 prompt(T1-6 必须新开会话,整段复制)
⚠️ 实施 T1-1~T1-5 的那个会话不能自测 T1-6——它已把三份文件全读进上下文, 三个探针必然"答得出",测出来是假绿。这与 P0-2 是同一个陷阱。
探针已核验唯一性(2026-08-28,grep -l 跑过全部 5 份 CLAUDE.md):
| 探针串 | 只出现在 | 标准答案 |
|---|---|---|
| 「七个模块已目录化」 | src-tauri/CLAUDE.md §4 | 七个 |
| 「有没有可读本地 URL」 | src/CLAUDE.md §4 | revisit.ts 的判据,不是「有没有 cached_file_path」 |
| 小标题「跨项目决策参考」 | admin/CLAUDE.md §2 | — |
text
这是一次只读实测,别改任何文件,别用 Agent/子会话。按顺序做五步,每步如实回答。
【第 1 步|先别看任何文件】
不要读取、grep、cat 任何文件。仅凭此刻上下文里已有的内容回答:
Q1. src-tauri/CLAUDE.md 的「Rust Commands 模块」那节说,有几个模块已经目录化了?
Q2. src/CLAUDE.md 里,src/lib/revisit.ts 的判据是什么?
Q3. admin/CLAUDE.md 里有没有一个叫「跨项目决策参考」的小标题?
上下文里没有就明确回答「没有,答不出」。不许猜、不许推理、不许"根据常见做法"作答。
同时报告:此刻上下文里出现了哪几份 CLAUDE.md?
【第 2 步|只触达 Rust】
用 Read 工具读 src-tauri/src/lib.rs 的前 20 行。只读这一个文件。
(必须用 Read,不能用 cat/head——注入挂在文件工具上,Bash 不触发,这是 P0-2 的实测结论。)
重答 Q1/Q2/Q3,并报告现在上下文里有哪几份 CLAUDE.md。
期望:Q1 答得出;Q2、Q3 仍答不出。
【第 3 步|只触达前端】
用 Read 工具读 src/lib/moduleNav.ts 的前 20 行。
重答 Q2,并报告上下文里有哪几份 CLAUDE.md。
期望:Q2 答得出。
【第 4 步|确认不该来的没来】
重答 Q3。期望:仍答不出(全程没碰 admin/ 任何文件)。
【第 5 步|结论 + 回写】
判定属于哪一种,一句话说清:
A. 三种场景都符合期望 → 阶段 1 收工
B. 有场景不符 → 具体是哪一步、期望什么、实际什么
把结论回写到 docs/plans/rvh-merge-plan.md 的 §1 台账 T1-6 那一行(状态 + 日期 + 判定),
并在 §3 T1-6 下面补一段「结果」。提交用:
git commit docs/plans/rvh-merge-plan.md -m "docs(plans): T1-6 离场检查 —— <判定>"
背景(读完就够):本仓刚把根 CLAUDE.md(73015 B)拆成
根(50542 B)+ src-tauri/CLAUDE.md + src/CLAUDE.md,加上原有的 admin/ 与 landing/ 共 5 份。
这一步验证拆分后的按需叠加真的按预期工作。全绿则阶段 1 收工,可以无限期停在那里。本会话已完成的机械前置(2026-08-28,T1-5 之后)
这些不需要新会话,已在实施会话里跑过:
| 检查 | 结果 |
|---|---|
pnpm run check:claude-paths(5 份全覆盖) | ✅ 退出码 0 |
node scripts/check-doc-links.mjs | 19 处悬挂,与拆分前逐条相同,新文件零新增 |
行级无丢失核对(对 git show HEAD:CLAUDE.md) | ✅ 见 T1-1 §D 验收第 3 条 |
| 红线索引 20 行 = 4 留根 + 16 下沉 | ✅ |
package.json / ci.yml 语法 | ✅ |
T1-6 只剩「新会话实测注入行为」这一件事。
结果(2026-08-28 新会话实测)
判定 = A:交接 prompt 定义的三个场景全部符合期望,按需叠加确实按预期工作。
| 步骤 | 动作 | 上下文里的 CLAUDE.md | Q1(七个) | Q2(revisit 判据) | Q3(跨项目决策参考) |
|---|---|---|---|---|---|
| 1 | 不碰任何文件 | 全局 + 根 = 2 份 | ⚠️ 见下 | 答不出 | 答不出 |
| 2 | Read src-tauri/src/lib.rs | +src-tauri/ = 3 份 | ✅ 答得出 | 答不出 | 答不出 |
| 3 | Read src/lib/moduleNav.ts | +src/ = 4 份 | — | ✅ 答得出 | 答不出 |
| 4 | 不碰 admin/ | 仍 4 份 | — | — | ✅ 仍答不出 |
两条如实备注,都不改变判定,但会影响下次复用这个 prompt 的人:
- 🔴 Q1 探针不干净,第 1 步就能答出「七个」。 唯一性核验(上面那张表)只
grep了字符串 「七个模块已目录化」,而根CLAUDE.md§2「已下沉的三小节」表里有一行写着 「Rust Commands 模块 →src-tauri/CLAUDE.md§4 → 七个已目录化模块的路径陷阱」—— 数字本身泄漏在恒加载的根文件里,只是措辞不同。本次靠「§4 正文的完整模块清单 (sync/pull三层嵌套等)第 1 步确实给不出」区分开了真假绿,但这是运气不是设计。 下次换探针:问「sync/pull拆成哪三块」或「reading 为什么是 5 块不是 3 块」, 根侧索引行结构上不可能含这类正文细节。教训与 P0-2 同源——探针唯一性要按语义核,不是按字符串核。 - 计划原表的三场景里,只有第 1 个被完整验证。 prompt 实际跑的是 「src-tauri 正向 + src 正向 + admin 反向」;原表的「只读
admin/lib/queries.ts→admin/CLAUDE.md加载」(正向)与「只读docs/product.md→ 只有根」两条没单独跑。 反向已足以证明「不该来的不来」这半边,而admin/CLAUDE.md的正向注入自合仓起一直在用、 有既有证据,故不另开会话补测。若将来对 admin 那份的挂载有疑,补一个只读admin/**的会话即可。
结论:阶段 1 收工,可以无限期停在这里。
4. 阶段 2 · RVH 侧收尾(必须 RVH 会话)
阶段不变量:全部在
~/reading_vocab_helper里做,不碰 RB 仓一个字。 目的是让 RVH 进入"可被整体搬运"的干净状态。 离场条件:T2-5 的 pre-merge tag 已推 origin,git status干净,只剩 master 一个分支。 ✅ 2026-08-28 达成:tagpre-rb-merge=7ffb05d已在 gitee;status 0 行; 只剩master+origin/master;无额外 worktree。本阶段三个 RVH 提交:76ec31a(T2-3) ·7ffb05d(T2-4),另 T2-1/T2-2 无新提交(先行收口 / 只删分支)。 会话隔离:CLAUDE.md §9 在合并完成前照旧有效——这一阶段必须是独立的 RVH 会话。
⚠️ 不变量的一处收窄(2026-08-28 补,阶段 1 收工时发现): 「不碰 RB 仓一个字」与 §0 规则 C「每完成一个任务必须回写 §1 台账」直接冲突——台账在 RB 仓。 裁定:不变量收窄为「不碰 RB 仓的代码与配置」,回写本计划的台账是唯一豁免。 理由:不变量的目的是「让 RVH 进入可被整体搬运的干净状态」,改一行 markdown 台账对此毫无影响; 而过期台账的代价是下个冷启动会话按错误进度开工,正是
docs/plans/README.md点名的病。 回写用绝对路径~/reading-browser/docs/plans/rvh-merge-plan.md, 在~/reading-browser里单独git commit <该文件>,不要混进 RVH 的提交。
交接 prompt(开 RVH 会话时整段复制)
⚠️ 必须是 RVH 会话(~/reading_vocab_helper,Flutter/Dart)。CLAUDE.md §9「会话隔离规则」 在合并完成前照旧有效:两端技术栈与工具链完全不同,混合上下文出错率高。
text
接手 RVH 并入 RB 仓计划的阶段 2(RVH 侧收尾)。本会话在 ~/reading_vocab_helper 做。
先做三件事,再动手:
1. 读 ~/reading-browser/docs/plans/rvh-merge-plan.md 的 §0 冷启动开工指引 + §1 进度台账
+ §4 阶段 2 全文(用绝对路径读——那是另一个仓,你的相对路径 docs/... 会解析到 RVH 自己的仓)。
那份文件是本任务的唯一真相源,每个任务都写了「会话/前置/动作/验收/回滚」,
不需要回看任何历史对话。阶段 0 与阶段 1 已全部 ✅ 收工。
2. 跑 `cd ~/reading_vocab_helper && git status --short && git worktree list && git branch -a`,
把实际状态与计划 T2-1/T2-2 里记的 2026-08-28 实测值比对。**对不上就先停下问用户**
——那意味着这几天有别的会话动过,计划里的清单已经过期。
3. 本阶段有两个决策要用户拍板,先问清楚再动手(见下)。
按 T2-1 → T2-5 顺序做。三条容易踩的:
- 🔴 **T2-1 的 test/fixtures/sm2-golden-vectors.json 是三端黄金向量。**
先 `git diff` 看它到底改了什么。**若 expected 值真的变了,停下**:必须先在 RB 会话同步
另外两个消费方(~/reading-browser 的 srs.rs::sm2_golden_tests 与 src/lib/sm2.test.ts),
否则合仓当天三端测试立刻红,而现场会以为是合并搞坏的。只是格式/注释变动则无妨。
- **T2-2 用 `git branch -d` 而不是 `-D`**:有未合并内容时 -d 会拒绝,那正是我们要的安全网。
- **T2-3 与 T2-4 是删除操作,动手前逐项向用户确认**(见下)。
两个需要用户拍板的决策:
① T2-3 个人文件:USER_NOTES.md(95KB)、USER_NOTES.md完整流程使用说明.pdf(157KB)、
QUICKSTART.md(6.5KB)——保留进公共仓 / 迁 docs/ / git rm --cached 转本地?
⚠️ 注意 git rm --cached 只动 HEAD,history 里还在,合并后仍会随历史进 GitHub 私有仓。
真要清得靠 filter-repo,那是阶段 3 的 T3-2 顺带做——所以这里的决定会变成 T3-2 的
--path 参数,请把结论明确写进台账 T2-3 那一行。
② T2-4 删死历史:supabase/migrations/ 与 tools/vocabulary_builder/ 两个目录确认可删?
计划里记了依据(前者 README 自称纯历史留痕、真相源已迁 RB;后者是被 v3 取代的旧 pipeline)。
删完必须 flutter analyze + flutter test 全绿;若变红说明还有活引用,停下来查清楚。
每个任务做完先跑验收命令,再回写台账,再提交。回写台账用绝对路径
~/reading-browser/docs/plans/rvh-merge-plan.md,并在 ~/reading-browser 里单独提交那一个文件
(`git commit docs/plans/rvh-merge-plan.md -m "..."`),不要混进 RVH 的提交。
RVH 自己的改动照常在 ~/reading_vocab_helper 提交。
本阶段边界:**只做阶段 2**。阶段 3 是原子区且必须回到 RB 会话做,不要在这次会话里开。
离场条件 = T2-5 的 pre-merge tag 已推 origin、git status 干净、只剩 master 一个分支。
做完就停——阶段 2 让 RVH 进入「可被整体搬运」的状态,本身不改变任何产品行为。T2-1 在途工作收口
会话:RVH · 前置:P0-5 = go
⚠️ 2026-08-28 实测:RVH 工作区有 16 个未提交/未跟踪文件,且 master ahead 6 未推。 其中两项是在途的跨端契约改动,必须先落地:
M test/fixtures/sm2-golden-vectors.json ← 三端黄金向量!改了就意味着 RB 侧两个消费方也要同步
A docs/cross-end/42-rvh-…-confirmation.md ← 42 号回执(RB 侧 40 号的回应)
M lib/features/sync/data/repositories/sync_repository_impl.dart
M lib/features/reading_tracking/data/...(2 个)
M lib/features/ocr/data/engines/mlkit_engine.dart
?? test/features/sync/reading_page_soft_delete_test.dart
?? test/features/ocr/mlkit_element_word_test.dart上面是 RVH 仓内的相对路径(
cd ~/reading_vocab_helper后的git status)。 42 号那条刻意用了 Unicode 省略号——写成完整路径会被check-doc-links.mjs当成本仓引用而误报悬挂 (本仓确实没有 42 号,它还在对端且尚未提交)。
动作:把这批工作正常做完、测试跑通、提交、推 origin。 sm2-golden-vectors.json 若真的改了内容,必须先在 RB 会话同步三端 (srs.rs::sm2_golden_tests + src/lib/sm2.test.ts),否则合仓当天三端测试立刻红, 而你会以为是合并搞坏的。
验收:
bash
cd ~/reading_vocab_helper && git status --short | wc -l # = 0
git rev-list --count origin/master..master # = 0
flutter test # 全绿这一项不做完,后面全部免谈。 合并会把未提交的东西整片丢掉(CLAUDE.md §8 路径 B)。
结果(2026-08-28,阶段 2 会话核对)
上面那份 16 文件清单已经过期——接手时工作区已干净、ahead 0,那批在途工作被后续会话 顺带收口了(361dd6d 回执 42 + 红线 #6h、6555c1e reading_pages 软删 + ML Kit 修复、 80ec5e7 推送后 ahead 归零)。验收三条当场复跑全绿: git status --short 空 · origin/master...master = 0 0 · flutter test 1359 passed / 5 skipped。
🔑 黄金向量的警报解除:361dd6d 对 test/fixtures/sm2-golden-vectors.json 的改动是 2 行,只动了 _doc 说明和 _consumers.rvh_dart 指向(从"待新会话"改成实际测试文件路径), 没有任何 expected 值变动。故 srs.rs::sm2_golden_tests 与 src/lib/sm2.test.ts不需要同步,合仓当天不会因此变红。
教训形状(与 P0-3 那条同族):计划里写死的"实测清单"过期得比想象中快。 冷启动会话必须先跑一遍状态比对再动手,不能拿计划里的快照当现状。
T2-2 删僵尸分支 + 清 worktree
会话:RVH · 前置:T2-1
2026-08-28 实测:claude/brave-tereshkova-9911f3 相对 master 是 63 behind / 0 ahead = 已完全并入,是僵尸分支。worktree elegant-turing-503c83 工作区干净、 HEAD 是 master 的祖先。
bash
cd ~/reading_vocab_helper
git worktree remove .claude/worktrees/elegant-turing-503c83
git branch -d claude/brave-tereshkova-9911f3 # -d 不是 -D:有未合并内容时会拒绝,正是我们要的
git worktree list && git branch -a # 只剩 master + origin/master为什么必须做:合并只搬一个 ref(master)。其他分支上的工作会被静默孤儿化。
T2-3 个人文件裁定
会话:RVH · 前置:T2-1
已 tracked 且会跟着进公共仓的:
| 文件 | 大小 | 建议 |
|---|---|---|
USER_NOTES.md | 95KB | 迁 docs/ 或 git rm --cached 转本地 |
USER_NOTES.md完整流程使用说明.pdf | 157KB | 同上(另:文件名含中文 + 空格,违反 RB 的 kebab-case 约定) |
QUICKSTART.md | 6.5KB | 保留,迁 docs/ |
个人记录.rtf | 26KB | 未 tracked,无需处理 |
注意:git rm --cached 只把它们从 HEAD 移除,history 里还在—— 合并后仍会随历史进 GitHub 私有仓。真要清得 filter-repo(T3-2 顺带做)。 先决定要不要清,再决定 T3-2 的 --path 参数。
T2-4 删死历史
会话:RVH · 前置:T2-1
| 目录 | 依据 |
|---|---|
supabase/migrations/ | 它自己的 README 第一行:「纯历史留痕,真相源已全部迁至 RB」。合仓后连留痕理由都没了——git history 就是留痕 |
tools/vocabulary_builder/ | 旧 pipeline,v3 已于 2026-07-19 迁入 RB tools/vocabulary_builder_v3/(红线 #10) |
验收:flutter analyze + flutter test 仍全绿(这两个目录不参与构建,应该无影响; 若有影响说明还有活引用,停下来查清楚)。
结果(2026-08-28)
flutter test 1359 passed / 5 skipped,与删除前逐字相同 —— 两个目录确实零活引用。
⚠️ flutter analyze 有一条 error,但它是噪声不是回归: tools/vocabulary_builder/fix_empty_definitions.dart:43 的 undefined_function。 该目录只是退出版本控制、文件仍在磁盘上(用户裁定保留 828M 本地产物), analyze 照旧扫它。删除前它就在报同一条。合仓后 CI 跑的是新克隆、不含该目录 ⇒ 自动消失。 其余 3305 条全是 info,tools/ 之外零 error。
引用改法(供 T4-1 参考):改的是现状断言(schema.md ×4 / decisions.md / deployment/README.md / cross-end-schema-compatibility.md / docs/README.md ×2 / doc-maintenance-guide.md),不改历史记述(CHANGELOG.md、docs/cross-end/19、 plan 里已勾选的 [x] 条目与 2026MMDD 占位路径)—— 后者记的是「当时确实有这个文件」。
T2-5 pre-merge tag
会话:RVH · 前置:T2-1 ~ T2-4
bash
cd ~/reading_vocab_helper
git tag -a pre-rb-merge -m "并入 RB 仓前的最后状态(docs/plans/rvh-merge-plan.md T2-5)"
git push origin master --tags这是回滚锚。阶段 3 出任何问题,RVH 仓从这个 tag 原样复活。
5. 阶段 3 · 合入(原子区)
阶段不变量:T3-2 到 T3-9 之间仓库处于半合并状态,main 会红。开工前确认有完整时间段, 且没有其他会话在动这两个仓。离场条件:T3-9 两端都能编译、全部守门绿、CI 绿。 回滚:任何一步失败 →
git reset --hard <T3-1 记录的 tag>。RVH 侧完全没动过(阶段 2 只是收拾自己)。
交接 prompt(开阶段 3 会话时整段复制)
⚠️ 必须是新开的 RB 会话(~/reading-browser)。做阶段 1 的那个会话上下文已塞满搬运细节, 而阶段 3 是一段长的原子操作,冷启动读 §0 更可靠。 ⚠️ 开工前确认两件事:① 你有一段完整、不会被打断的时间; ② 没有其他会话在动这两个仓——本仓 2026-08-28 就有另一个会话在做 vocab/verification, 阶段 3 期间 main 是红的,并行改动会让"是谁弄红的"无法分辨。
text
接手 RVH 并入 RB 仓计划的阶段 3(合入)。本会话在 ~/reading-browser 做。
⚠️ 这是原子区:T3-2 到 T3-9 之间 main 是红的(skill 会误判 rvh/、CI 还没有 Flutter job)。
中途停下 = 仓库半合并状态。开工前确认你有完整时间段,且没有别的会话在动这两个仓
(跑 git status --short 与 git worktree list 确认;不干净就先停下问用户)。
先读,再动手:
1. 读 docs/plans/rvh-merge-plan.md 的 §0 冷启动开工指引 + §1 进度台账 + §5 阶段 3 全文。
那份文件是唯一真相源,每个任务都写了「会话/前置/动作/验收/回滚」,
不需要回看任何历史对话。阶段 0、1、2 已全部 ✅ 收工。
2. 特别读 §0 的「禁止事项」表——头两条在本阶段随时可能被踩。
四条已经裁定好、不要再重新决策的:
- 🔴 **filter-repo 只许对 /tmp/rvh-prefixed 这个临时克隆跑。**
任何情况下不许对 ~/reading-browser 或 ~/reading_vocab_helper 跑。
改写 RB 历史会让所有发布 tag 的 SHA 全变,而红线 #11 的
src-tauri/src/db/shipped_migrations.txt 真相源就是发布 tag。
- ✅ **T3-2 的 --path 参数已定死两组,都必须加**(别再问用户):
① P0-3 裁定:rvh/.env.staging + rvh/assets/.env.staging(已弃用的 Baidu OCR 凭据)
② T2-3 用户裁定:rvh/USER_NOTES.md + 'rvh/USER_NOTES.md完整流程使用说明.pdf'
(PDF 文件名含中文,务必带引号)
补一条验收:git log --all --oneline -- 'rvh/USER_NOTES*' | wc -l 必须 = 0
⏭ 不必清 31 个历史 dict blob——P0-4 已确认 Gitee 配额够,无体积压力。
- ✅ **不用 git subtree add**(admin 用的那个)。T3-2 里有实测证据:subtree 会让按路径
过滤的 log/blame 在合并点断掉。真正判据是
`git log --oneline -- rvh/lib/core/algorithms/sm2_algorithm.dart | wc -l` > 1。
- ✅ **黄金向量不需要同步**:阶段 2 已确认 sm2-golden-vectors.json 只改了 _doc/_consumers
两个说明字段,expected 一个没动。
按 T3-1 → T3-9 顺序做,两个最容易做砸的:
- **T3-7 要改两处,不是一处。** 除了 scripts/check-claude-md-paths.mjs 的 ROOTS 白名单,
还要把 rvh/CLAUDE.md 加进 package.json 的 check:claude-paths 文件清单——
阶段 1 把那条命令从「无参数」改成了显式清单,漏加则 CI 静默跳过,
而 T3-7 的手动验收命令照样全绿。任务正文里写了两条验收,两条都要跑。
- **T3-8 决定这次合并到底有没有收益。** RVH 的 CLAUDE.md 有 97,904 B,其中 68% 是
RB 红线的 Dart 侧镜像。原样搬 = 把第二本账换个位置继续记,收益归零。
做法见任务正文(契约文字并进根 §4,Dart 侧落地形状留 rvh/ 但改成引用)。
⚠️ 但**不要贪多**:#6d 那条契约的真正收敛是 T4-4 的活,T3-8 做到「rvh 侧不再复述」即可离场。
回滚锚已就位,任何一步失败都能原样退回:
RB → git reset --hard pre-rvh-merge(T3-1 里打,先打再动手)
RVH → tag pre-rb-merge → 7ffb05d,已推 gitee;阶段 3 全程不碰 RVH 仓
离场条件 = T3-9 全绿:两端都能编译(cargo check + pnpm build + flutter analyze/test)、
全部守门绿、check-doc-links 的悬挂数不比合并前多。全绿才提交并推,
推之前确认 Gitee 侧接受这个体积。
本阶段边界:**只做阶段 3**。阶段 4 每项独立、可分多会话、可乱序,不要在这次会话里顺手开。T3-1 双仓备份
会话:RB · 前置:阶段 2 全部 ✅
bash
cd /Users/larry/reading-browser
git tag -a pre-rvh-merge -m "RVH 合入前的最后状态(docs/plans/rvh-merge-plan.md T3-1)"
git push github pre-rvh-merge && git push origin pre-rvh-merge
# 本地镜像(filter-repo 会拒绝在非 fresh clone 上跑,这份镜像也是 T3-2 的输入)
git clone --mirror ~/reading_vocab_helper /tmp/rvh-mirror.git
du -sh /tmp/rvh-mirror.git验收:两个 tag 都推上去了;镜像克隆存在且体积与源仓 .git 相当。
T3-2 在临时克隆里造前缀化历史
会话:RB · 前置:T3-1
为什么不用 git subtree add(admin 用的那个)—— 有实测证据:
$ git log --oneline -- admin/lib/queries.ts | wc -l
4 ← 全是合并之后的
$ git log --oneline -- admin/lib/queries.ts | tail -1
1d5a3e2 Add 'admin/' from commit '6c3ec74...'subtree add 把历史接进了 DAG,但那些老 commit 里的路径是 lib/queries.ts 而不是 admin/lib/queries.ts,于是按路径过滤的 log / blame 在合并点断掉。 admin 只有 58 个文件、两个月历史,代价小;RVH 是 504 个 Dart 文件、429 条 commit、8 个月, 且本项目的红线大量来自"当初为什么这么写"——blame 是活资产(评估文档 §7 否决 squash 导入 同一理由)。
改用 filter-repo 把路径真正前缀化:
bash
cd /tmp && git clone /tmp/rvh-mirror.git rvh-prefixed && cd rvh-prefixed
git filter-repo --to-subdirectory-filter rvh
# ✅ 已裁定必加(P0-3:清掉那对已弃用的 Baidu OCR 凭据)。
# 注意路径已被上一条命令加上 rvh/ 前缀,这里要写前缀后的路径:
git filter-repo --path rvh/.env.staging --path rvh/assets/.env.staging --invert-paths
# ✅ 已裁定必加(T2-3:用户要求把个人文件从历史里清掉)。
# 两条路径都要写全——PDF 那个文件名含中文,务必带引号:
git filter-repo --path rvh/USER_NOTES.md \
--path 'rvh/USER_NOTES.md完整流程使用说明.pdf' --invert-paths
# 验收补一条:`git log --all --oneline -- 'rvh/USER_NOTES*' | wc -l` 必须 = 0
# ⏭ 不必做:清 31 个历史 dict blob —— P0-4 已确认 Gitee 配额足够,无体积压力验收:
bash
cd /tmp/rvh-prefixed
git log --oneline | wc -l # ≈ 429,历史条数没少(除非显式清了路径)
git ls-files | head -3 # 每行都以 rvh/ 开头
git log --oneline -- rvh/lib/core/algorithms/sm2_algorithm.dart | wc -l # > 1,历史穿透最后一条是本任务的真正判据——它正是 subtree add 做不到的那件事。
⚠️ filter-repo 只允许对
/tmp/rvh-prefixed这个临时克隆跑。 任何情况下不许对~/reading-browser或~/reading_vocab_helper跑(§0 禁止事项第一条)。
T3-3 合入
会话:RB · 前置:T3-2
bash
cd /Users/larry/reading-browser
git remote add rvh-tmp /tmp/rvh-prefixed
git fetch rvh-tmp
git merge --allow-unrelated-histories rvh-tmp/master -m "feat(rvh): RVH 移动端并入本仓 rvh/ —— 双端契约获得唯一物理归属
按 docs/plans/rvh-merge-plan.md 阶段 3 执行。历史经 filter-repo 前缀化,
git log/blame 可穿透合并点(与 admin 的 subtree add 不同,见该计划 T3-2)。"
git remote remove rvh-tmp验收:
bash
ls rvh/pubspec.yaml rvh/lib rvh/CLAUDE.md
git log --oneline -- rvh/lib/core/algorithms/sm2_algorithm.dart | wc -l # > 1
git status --short # 干净此刻 main 是红的(skill 会误判 rvh/、CI 没有 Flutter job)。继续往下,别停。
T3-4 忽略规则与 MCP 就位
会话:RB · 前置:T3-3
.gitignore:RVH 那份 4.3KB 的随目录进来成为rvh/.gitignore,git 原生支持嵌套。 核对根.gitignore没有与之冲突的规则。确认rvh/build/rvh/.dart_tool/rvh/logs/(397 项!)rvh/temp/rvh/backups/都被忽略。🔴 2026-08-31 勘误:这一步当时的结论(用
!rvh/data/反选、不锚定根规则) 是错的,理由与后果见上面状态表 T3-4 那行的勘误段。一句话:无锚定的data/连rvh/lib/**/data/这种 Clean Architecture 层名一起吞,共 110 个已跟踪文件; 而它本要保护的 vb3 大文件根本不靠它。别照这一步的写法处理任何新的忽略规则。.gitattributes:根那份写着* text=auto eol=lf(Windows CI 的 CRLF 坑)。 Dart 文件适用,无需改。但要给rvh/assets/databases/*.db加binary(比照现有那行src-tauri/assets/lampio_dict.db binary)。.mcp.json:加rvh-debug(8 个工具),路径从/Users/larry/reading_vocab_helper/rvh-debug-mcp/dist/index.js改为rvh/rvh-debug-mcp/dist/index.js。
验收:git status --short 干净(没有本该被忽略的构建产物冒出来)。
T3-5 Skill 作用域
会话:RB · 前置:T3-3
RB 侧四个 dev-flow skill 必须排除 rvh/,否则会拿 RB 的不变式(hex 字面量 / ListItem / invoke 收敛 / cargo+vite build)去判 Dart 代码。
模式已经固化,照抄 admin 的做法(.claude/skills/arch-check/SKILL.md:105 明写 「保持路径限定在 src/ / src-tauri/,禁止写成根级 glob」):
| skill | 改法 |
|---|---|
/arch-check | 规则本就硬绑 src/ src-tauri/ docs/plans/,核对没有根级 glob 即可;在作用域注释里补一句 rvh/ |
/code-review | 增量模式的 grep -v '^admin/' 加一条 grep -v '^rvh/' |
/build-check | Step 2 的变更分流 grep -E '^(admin|landing)/' 加 rvh;Step 5 加 rvh 分支(flutter analyze + flutter test) |
/ui-check | 同 arch-check,核对路径限定 |
/doc-sync-check | 加 rvh/ 文档映射 |
RVH 侧八个 skill 随目录进来成为 scoped:rvh:code-review / rvh:clean-arch-check / rvh:preinstalled-db-update / rvh:i18n-check / rvh:ui-compliance-check / rvh:doc-sync-check / rvh:doc-consistency-check / rvh:code-check-before-restart。 同名冲突(code-review / doc-sync-check 三方同名)由 scoping 处理,admin 已验证。
验收:在只改了 rvh/lib/** 的状态下跑 /arch-check 与 /build-check, 两者都应正确识别为"不属 RB 范围"而不是报一堆假违规。
T3-6 新增 ci-rvh.yml
会话:RB · 前置:T3-3
照抄 ci-admin.yml 的形状,包括它头注释里那条踩过的坑: on.paths 是 workflow 级不是 job 级,所以必须独立成文件, 不能往 ci.yml 加 job(否则会把桌面端的门一起过滤掉)。
paths: ['rvh/**', '.github/workflows/ci-rvh.yml']- RVH 现有
ci.yml的 4 个 job(analyze / architecture / test / coverage)原样搬 - 所有
run:步骤加working-directory: rvh - architecture job 里的 grep 路径
lib/features/...→rvh/lib/features/...(或用 working-directory) FLUTTER_VERSION按 P0-1 的发现决定对齐还是钉旧版并注明理由- 删掉
rvh/.github/(合过来的那份不会被 GitHub 识别,留着是误导)
验收:只改 rvh/ 下文件推一个分支 → 只触发 ci-rvh;只改 src/ → 不触发 ci-rvh。
T3-7 ROOTS 加 rvh
会话:RB · 前置:T3-3
scripts/check-claude-md-paths.mjs 有一个顶层目录白名单:
const ROOTS = 'src|src-tauri|docs|scripts|tools|supabase|admin|landing|rb-debug-mcp|public';不加 rvh,rvh/CLAUDE.md 里的仓内路径引用一条都不会被校验。
🔴 要改的是两处,不是一处(2026-08-28 阶段 1 之后新增的耦合): T1-5 把 package.json 的 check:claude-paths 从「无参数、只查根」改成了显式文件清单:
"check:claude-paths": "node scripts/check-claude-md-paths.mjs CLAUDE.md src-tauri/CLAUDE.md src/CLAUDE.md admin/CLAUDE.md landing/CLAUDE.md"只加 ROOTS 白名单而不把 rvh/CLAUDE.md 加进这行,CI 会静默跳过它。 而本任务下面那条验收命令是手动传参的,那种情况下照样全绿—— 典型的「验收通过但闸门没覆盖」。ci.yml 的步骤注释里已写了「新增一份要加进去」。
验收(两条都要跑):
bash
# ① 脚本本身能校验 rvh/:先故意写一条不存在的 `rvh/lib/nonexistent.dart` 验证它会红,再删掉
node scripts/check-claude-md-paths.mjs rvh/CLAUDE.md
# ② CI 实际会跑到它:这条的输出里必须出现 rvh/CLAUDE.md
grep -o 'check-claude-md-paths.mjs.*' package.jsonT3-8 rvh/CLAUDE.md 去镜像 + 根加路由
会话:RB · 前置:T3-3、阶段 1 全部 ✅
这一步决定合并到底有没有收益。
RVH 的 CLAUDE.md 有 97,904 B,其中「跨端 Sync 协议红线」一节 = 728 行 / 67KB(68%), 是 #5b/#5d/#5e/#5f/#5g/#5h/#6b/#6c/#6d/#6e/#6f 的 Dart 侧镜像。
原样搬 = 把第二本账换个位置继续记,收益归零(§0 禁止事项第三条)。
正确做法:
- 契约文字("为什么"、判据、反例、事故记录)→ 并进根
CLAUDE.md§4 对应条目。 同一条红线从此只有一处叙述。 - Dart 侧的落地形状与 grep 守卫("RVH 这边具体长什么样")→ 留
rvh/CLAUDE.md, 但改写成指向根条目 + 只讲本端差异。 - 结果目标:
rvh/CLAUDE.md≈ 10k tokens(当前 ~27.9k)。
同时在根 CLAUDE.md 顶部路由段加一句「只碰 rvh/ 就直读 rvh/CLAUDE.md」, 并更新 §2 仓库地图:卫星从四颗变五颗(admin / landing / supabase / rb-debug-mcp / rvh)。
验收:
bash
wc -c rvh/CLAUDE.md # 目标 < 40000 B
grep -c '#5[b-h]\|#6[b-f]' rvh/CLAUDE.md # 应大幅下降(改成引用而非复述)
node scripts/check-claude-md-paths.mjs CLAUDE.md rvh/CLAUDE.md src-tauri/CLAUDE.md⚠️ 这一步不要贪多。#6d 那条契约的收敛(真正把两处叙述合成一处、 并让两端守卫共享判据)是 T4-4 的活。T3-8 只要做到"rvh 侧不再复述、改为引用"即可离场。
T3-8 结果(2026-08-28)
做到的:13 条红线里,凡在 RB 侧有叙述的(#5b · #5d · #5e · #5g · #5h · #6d · #6e · #6f · #6g) 一律改成指针,正文只留「RVH 这边长什么样 + 守卫判据 + 本端独有的坑」。 新增一张跨仓编号对照表(两仓 6x 段编号确实分叉:RVH #6d = last_opened_at,RB #6d = 墓碑 synced_at)。 根侧同步加了 rvh 路由行、五颗卫星的仓库地图、以及把 RVH 实测的 27 秒触发门槛并进根 §4 #6d (那里原写「几秒漂移就够」,是按 autoSync 60s 估的理论最坏值 —— 两处叙述已经漂了,这正是合仓要治的病)。
没做到 <40000 B,最终 78365 B。 不是偷懒,是正文那个数字建立在一个偏高的估计上 (「67KB / 68% 是 RB 红线的 Dart 侧镜像」)。实际逐条核对后:
| 成分 | 字节 | 能不能删 |
|---|---|---|
| 11 个 grep 守卫块 | ~11.8k | 不能 —— 正文自己写明「grep 守卫留 rvh/CLAUDE.md」 |
| #5f · #6b · #6c 三条 | ~8k | 不能 —— 2026-08-28 实测 RB 的 CLAUDE.md / src-tauri/CLAUDE.md 里根本没有这三条(grep cover_image_data / on_conflict / INSERT OR REPLACE 全部 0 命中)。RVH 原文写的「RB 端等价红线见 ~/reading-browser/CLAUDE.md 同条款」是条坏指针,指向不存在的条款。删了 = 契约彻底消失 |
| RVH 独有的证据与落地形状 | ~20k | 不能 —— 无处可去(#5d per-user watermark · #5h 非竞态触发路径 · #6f 撤销对称性 · #6h 全条 · #5g OCR 采集闸门 · 键空间规则) |
| 其余非红线小节 | ~23k | 不在本任务范围 |
要压到 40000 B,只能删掉守卫块或删掉那三条唯一叙述 —— 两者都与本计划的目的相反。 建议把验收改成「RB 侧已有叙述的条目 0 复述」(可核,且正是想要的东西), 而不是一个字节数。字节数收敛的真正机会在 T4-4(红线合一)与「把 grep 下沉成脚本」。
T3-9 离场检查
会话:RB · 前置:T3-1 ~ T3-8
bash
# 桌面端
cargo check --manifest-path src-tauri/Cargo.toml
pnpm build && pnpm test:run
# 移动端
(cd rvh && flutter analyze && flutter test)
# 守门
pnpm run check:claude-paths
pnpm run check:no-inline-zh && pnpm run check:no-inline-en
pnpm run check:schema-frozen
node scripts/check-doc-links.mjs # 允许仍有 §T4-1 待处理的悬挂,但不许比合并前更多
# 体积
du -sh .git全绿则原子区结束,可以提交并推。推之前确认 Gitee 侧接受这个体积(P0-4)。
6. 阶段 4 · 兑现收益
阶段不变量:每一项独立、可乱序、可分多会话。任何一项做不完都不影响其他项。 这一阶段没有"离场条件"——做多少算多少,但 T4-2 是整个合并的最大收益,优先做。
开工指引(阶段 4 没有、也不需要阶段 3 那种交接 prompt)
阶段 3 有整段交接 prompt,是因为它是原子区(中途停下 = 半合并状态)。 阶段 4 每项都能独立收工,所以开工只要三句话:
text
做 docs/plans/rvh-merge-plan.md 的 T4-<N>。本会话在 ~/reading-browser。
先读该文件的 §0 冷启动开工指引 + §1 台账 + §6 里 T4-<N> 的正文,那里写了动作与验收。
做完回写 §1 台账再提交。每项都要遵守的三条(和阶段 3 同源,别因为"这阶段很轻"就跳过):
- 一次只做一项,做完就提交。 阶段 4 各项互不依赖,攒一起提交只会让出问题时分不清是谁弄的。
- 禁止改
docs/plans/archive/与docs/cross-end/里的历史记述(§0 禁止事项第四条)—— 它们是写作当时的事实记录。只改操作性文件:脚本 / skill / CLAUDE.md / 活跃 plan / README 索引。 - 禁止改写 git 历史(§0 禁止事项第一条)。合入已推双远端,
filter-repo/rebase/ 强推这条路已经关死。
建议顺序(不是硬依赖,只是省返工):
| 顺序 | 项 | 为什么排这里 |
|---|---|---|
| 1 | T4-1 ✅ | git mv 会让闸门 19 → 22。且「编号复原」改判为「不重编号 + README 消歧」。详见 T4-1 正文「实际做法」 |
| 2 | sm2-golden-vectors.json,那正是 T4-3 的内容 —— 分开做必返工 | |
| 3 | 其余(T4-4 ~ T4-9) | 随时,互不依赖 |
T4-9 之前先看一眼:老仓一归档,rvh/local_packages/opencv_dart(100MB,gitignored、不在仓里) 就只剩本机一份了。见 rvh/QUICKSTART.md 开头那节。
关于"rvh/ 要不要改成 mobile/ 之类的角色名"(2026-08-29 问过,结论:不改): 已推送 ⇒ 不能再用 filter-repo 干净改名(红线:改写 RB 历史会让发布 tag 的 SHA 全变)。 只剩 git mv,而实测 git mv rvh mobile 之后 git log -- <该文件> 6 → 1、git log -- <整个目录> = 1(--follow 对目录无效, 只有单文件加 --follow 才是 7)—— 正是 T3-2 用来否决 git subtree 的那条判据。 git blame 仍能穿透。且仓内 281 个文件、934 个路径名带 rvh(ci-rvh.yml · rvh-debug-mcp/ · 8 个 rvh:* skill · NN-rvh-*.md),只改目录名会更不一致。 真要统一,那是「退役 RVH 这个昵称」的独立项目,且应排在 T4-1 / T4-4 之后(那两项本就要大改这批文档)。
T4-1 docs/cross-end/ 归并 + 编号复原 ✅(2026-08-29)
把 rvh/docs/cross-end/ 的 30 份并进根 docs/cross-end/,恢复 01–42 的单一序列, 更新 docs/cross-end/README.md 的编号总表。
🔴 实际做法(与上面这段计划有三处出入,动手前实测得出)
① 数量:29 份 .md + 5 个数据文件 = 34 个文件,不是 30 份。
② 「恢复单一序列」这个前提是错的。 两侧不是一条序列被拆成两半,而是 从 04 号起各自往下发号的两条计数器,03–22 之间有 15 个号撞车 (03 04 05 06 09 10 12 13 14 16 18 19 20 21 22),且绝大多数同号不同主题。 17 号之后两侧才重新对齐。
③ 于是「编号复原」改判为「不重编号 + README 消歧」。 三条理由:
- 15 个重号里有 11 个被至少一份冻结文档按文件名引用(handoff 正文 / 已归档 plan / CHANGELOG / Rust·Dart·SQL 源码注释)。改名 = 在 §0 明令「禁止改」的文件里造新死链 —— 这条禁令与「重编号」直接冲突,冲突时按 §0 解:改索引和引用,不改叙述。
- 01–46 只剩 41 一个空号,重编号只能排到 47+,会把 6 月写的文档标成「第 47 号」,
NN的时序语义(CLAUDE.md §12)当场作废。 - 既有先例:
docs/cross-end/README.md的「12 重号」一节早就为同一情形裁过 「不改名,以本表为准」。本次只是把它从 1 个号推广到 15 个号。
消歧靠 docs/cross-end/README.md 新增的重号总表(15 组),不靠文件名。
④ 只有 2 个是真的文件名冲突(会被 mv 覆盖),都做了去重: 21-rb-push-user-scoping-confirmation.md 两侧字节相同; 03-rvh-debug-mcp-design.md 两侧仅差 3 行路径指针(RB 那份 2026-08-02 维护过、更新), 保留 RB 那份。副作用:rvh/docs/plans/rvh-debug-mcp-plan.md(状态"未启动")里 "cp 一份保持双端对称"的步骤已作废,已在那份活跃 plan 里注记。
⑤ 计划漏了一个坑:naive git mv 会让闸门变得更糟。 移入的 handoff 正文里写着 rvh-相对的 docs/plans/〈某某〉.md,在老仓解析正确, 搬迁后指向空气 —— 实测新增 12 处悬挂,19 → 22 而不是 → 10。 处置(用户 2026-08-29 明确同意):只改定位符、不动叙述,8 处补 rvh/ 前缀、 4 处补 archive/(那 4 处在老仓里就是漏了跨仓前缀的死链,合仓反而修好了它们)。 判据:§0 保护的是历史记述,路径是定位符;不改等于让搬迁行为默默篡改文档含义。 先例 —— 根 03-rvh-debug-mcp-design.md 自己 2026-08-02 就被这样维护过(3 行全是路径修正)。 另修 4 处被搬迁打断的 markdown 相对链接(1 处真断 + 3 处 ../../../reading-browser/ 跨仓逃逸)。
⑥ 顺带发现的反面事实:绝大多数 rvh 侧引用写的是 docs/cross-end/NN-*.md, rvh-相对与仓根相对在这里恰好重合,所以 rvh/lib/**/*.dart(15 处)· rvh/assets/sql/01_create_tables.sql(6 处)· rvh/test/** · rvh/docs/database/schema.md · rvh/docs/guides/ · rvh/.claude/skills/ 一个字都不用改。真正要改的是写 rvh/docs/cross-end/(rvh/CLAUDE.md 11 处 + 根 CLAUDE.md 1 处,check:claude-paths 守着) 与写 ../cross-end/ / ](cross-end/(rvh/docs/README.md · backlog.md · scan-ocr-...-plan.md · decisions.md · rvh/CHANGELOG.md)的那些。
⑦ rvh/docs/README.md 的 30 行 cross-end 索引已折叠成一行指针。 那是同一个目录的第二本账,合仓后必然漂移;根 README 是唯一索引。
顺带修一道恒红的闸门:node scripts/check-doc-links.mjs 当前报 19 处悬挂引用, 其中 8 处的目标文件都存在、只是在 RVH 仓(25/27/29/32/35/36/37/39-rvh-*), 另 1 处是 31-rb-round30-reseed-handoff.md → 32-rvh-round30-confirmation.md。 归并后这 9 行自动变绿,一处引用都不用改。
⚠️ 脚本末尾那句诊断「多半是 plan 归档后没跟着改路径——补上 archive/ 即可」 对这 8 条是错的,照它去修会指向不存在的 archive/ 路径。 ✅ 已改准(2026-08-29):拆成三种成因逐条列出(① 补 archive/ ② 补 rvh/ 前缀 ③ 省略号占位符不是路径),并提示先 find 定位再决定改法。 进 CI 仍未做(脚本至今不在 ✅ 2026-08-29 T4-2 收尾时做掉:剩余 10 处中 7 处补 .github/workflows/ 也不在 package.json),留给 T4-2。archive/ 前缀、3 处占位符 (24-...-handoff.md 与 NN-*-confirmation.md)改由脚本的 PLACEHOLDER_RE 排除, 降到 0 后加进 package.json 的 check:doc-links 并接入 ci.yml。详见 T4-2 结果 §4。
不要改 handoff 正文里的历史记述(§0 禁止事项第四条)—— 它们是写作当时的事实记录。只改操作性文件:README 索引、脚本、skill、活跃 plan。
T4-2 cross-end-check.sh 相对化 → 进 CI ★ ✅(2026-08-29)
这是整个合并的最大单项收益。 scripts/cross-end-check.sh 331 行、 11 处硬编码 $RVH_ROOT。今天 grep -rn 'cross-end' .github/workflows/ = 空—— 它跑不了 CI,因为 CI 里没有第二个仓。
改法:RVH_ROOT="${RVH_ROOT:-$HOME/reading_vocab_helper}" → RVH_ROOT="${RVH_ROOT:-rvh}", 其余 10 处路径自然跟随。然后接进 CI(触发条件应同时覆盖 src-tauri/** 与 rvh/**, 因为它守的正是两端漂移)。
同时 §D 那段「grep 1.3 + 打印一份散文写死的期望值供人眼复核」的 heuristic 可以换成真正读 docs/cross-end/sm2-golden-vectors.json(配合 T4-3)。
🔴 开工前实测(2026-08-29,T4-1 收尾时顺手做的只读探查)
记在这里而不是只留在开工 prompt 里 —— 上面那段正文写于合仓前,已有多处与现状不符。 T4-1 的教训正是「正文把事情说简单了,而修正只留在对话里」。照上面正文直接改会踩坑。
🔴 A. 脚本现在跑不到底 —— 接 CI 之前必须先修两个 bug。 ✅ 已修(2026-08-29,见下方「A 的收尾」)RVH_ROOT="$PWD/rvh" bash scripts/cross-end-check.sh 实测在 §B 结束处崩溃, A/B 两段之后一行不出,## 汇总 从未打印:
## B. 预装词库(红线 #10)
✅ lampio_dict.db byte-equal(06cbb4dcde22…)
scripts/cross-end-check.sh: line 129: rvh_ver?: unbound variable
scripts/cross-end-check.sh: line 129: TMP_RVH_DB: unbound variable- bug ①(第 129 行):
ok "…RVH v$rvh_ver)—— …"—— 裸$rvh_ver后紧跟全角), bash 3.2 把那个多字节标点的字节并进变量名,set -u下判为 unbound 直接终止。 加花括号${rvh_ver}即可(同一段第 127 行的 skip 分支本来就写了花括号, 只有 else 分支漏了)。⚠️ 与 locale 无关 ——LANG=en_US.UTF-8下逐字复现。 - bug ②(第 44 行 trap):
trap 'rm -f "$BUF" "$TMP_RVH_DB" "$TMP_RB_DB"' EXIT在第 44 行注册, 而TMP_RVH_DB到第 157 行才初始化。任何在 157 行之前的提前退出都会让 trap 自己再炸一次, 清理不执行。bug ① 触发的正是这条。 - 为什么以前没暴露:第 129 行在 else 分支(两端版本锚都提取成功)里, 此前应是某一端提取为空、恒走第 127 行的 skip 分支。合仓后首次走进 else,一走进就崩。 具体是哪一端、从哪个 commit 起变的,T4-2 会话可用
git log -L124,130:scripts/cross-end-check.sh查。 - ⚠️ 本机是 bash 3.2(macOS 系统 bash),CI 上
ubuntu-latest是 bash 5.x。 bug ① 在 bash 5 下未必复现 —— 这正是「本机红 CI 绿」或反过来的经典陷阱。 接 CI 前请在 Linux/bash5 上各跑一次,别只信一边。
A 的收尾(2026-08-29 已修,commit 见台账) —— 三点值得下一个会话知道:
- 按模式扫,抓到了第二处。只修报错那一行是 workaround;用
perl -ne 'while(/\$([A-Za-z_]\w*)([^\x00-\x7F])/g){...}'全扫, 除第 129 行外还有第 233 行$GOLDEN_JSON)—— 它在「黄金向量 JSON 缺失」的 else 分支里,平时走不到,是潜伏的同款。两处都改成${var},复扫已清零。 - 陷阱写进了脚本头(
set -uo pipefail下方),含一行自检命令, 防止以后再写出「变量后直接接中文标点」。 - bug ② 的修法是把
TMP_RVH_DB=""; TMP_RB_DB=""提到 trap 注册之前 (原在 §E 第 ~157 行),§E 那句改成注释。根因是「trap 引用的变量必须先于 trap 存在」, 不是给 trap 加:-兜底。
🔴 A 修完立刻暴露出 3 个此前被掩盖的 ❌(脚本崩在 §B,§E 从来没跑过):
## 汇总:✅ 15 ❌ 3 ⚠️ 0 (退出码 1)
❌ reading_notes 列集合漂移 — 仅 RB:[source_platform] 仅 RVH:[∅]
❌ reading_pages 列集合漂移 — 仅 RB:[cached_file_path content_html] 仅 RVH:[linked_at]
❌ known_words 列集合漂移 — 仅 RB:[∅] 仅 RVH:[added_at]本次未处置(超出「修 bug」范围,且涉及 RVH schema,按 §9 需分会话判断)。 脚本自己在 §E 开头就写了「本节差异需人工判断是否 local-only」—— 凭直觉这三条很可能都是合法的 local-only 列(cached_file_path/content_html 是 RB 快照,RVH 无阅读功能;added_at/linked_at 像是 RVH 本地列), 但没核实过,别当结论用。
⚠️ 这里有个设计矛盾,是 T4-2 的真正核心:脚本第 163–165 行自己就写着 「列差异 ≠ 一定是漂移…… 本节差异需人工判断是否 local-only」, 甚至把 cached_file_path/content_html 直接点名为合法 local-only 的例子 —— 可它用的是 bad() 而不是 skip()/say(),于是这三条照样计入 FAIL、把退出码顶成 1。 一个自称「需人工判断」的检查项天然与 CI 的二元判定冲突。 接 CI 不只是加个 workflow,得先决定 §E 到底算不算硬闸门。
⚠️ 这三条直接决定 T4-2 能不能接 CI:接进去的那一刻它就是红的。 两条路 —— ① 逐条核实后判为 local-only,则把它们加进脚本的豁免名单 (RB_ONLY_TABLES 那种机制,但这里是列级豁免,脚本目前只有表级); ② 判为真漂移,则先修漂移。在此之前不要接 CI。
🔴 B. §D 已经改造完了 —— T4-2 与 T4-3 的耦合已经消失。 上面正文说「§D 可以换成真正读 JSON(配合 T4-3)」,但第 216 行早已是 GOLDEN_JSON="$RB_ROOT/docs/cross-end/sm2-golden-vectors.json" + python3 现推打印, 第 210–214 行还留了注释明写「散文期望值是第二本账、已删」。 ⇒ §6「建议顺序」第 2 行「T4-2 + T4-3 一起做,分开做必返工」的理由已不成立, T4-3 可以独立排期。(真要说还剩什么,是 rvh/test/fixtures/sm2-golden-vectors.json 那份逐字节副本还没改成引用 —— 那纯属 T4-3,与本项无关。) ⇒ 顺带:根 CLAUDE.md §9 里那句「⚠️ 但 scripts/cross-end-check.sh §D 仍是 heuristic (grep 1.3 + 打印一份散文写死的期望值)…… 那份散文与 JSON 是两本账」已经过期, 是恒加载文件里的一张错误地图。T4-2 顺手订正(或并进 T4-7 一起处理 §9)。
C. 数字订正 + 一个路径基准的坑。 331 行 ✅;RVH_ROOT 实际出现 14 次(第 20 行定义 + 第 59 行提示文案 + 12 处使用), 正文写的「11 处」偏低。⚠️ 正文给的改法 RVH_ROOT="${RVH_ROOT:-rvh}" 不要照抄: 第 20 行 RB_ROOT="$(cd "$(dirname "$0")/.." && pwd)" 是按脚本自身位置推导的绝对路径 (作者刻意做成 cwd 无关),旁边塞一个裸相对路径等于在同一个脚本里混用两种基准, 换个 cwd 跑就坏。应写 RVH_ROOT="${RVH_ROOT:-$RB_ROOT/rvh}"。 好消息:第 57 行有 [ ! -d "$RVH_ROOT" ] && exit 1 的目录守门,整个指错至少是硬失败、不会假绿。
D. 退出码语义:SKIP 不计入失败。 第 331 行 exit $([ "$FAIL" -gt 0 ] && echo 1 || echo 0) —— skip() 只加 SKIP 计数。 ⇒ 单个文件路径指错的那一项会走 skip 分支、退出码仍是 0。接 CI 时若不额外约束 (比如「SKIP 超过基线即失败」,或至少把 SKIP 数打进 job summary), 就会得到一道看起来绿、其实什么都没查的闸门。这是 §C 目录守门挡不住的那一半。
E. 搭车项:scripts/check-doc-links.mjs 同样不在 CI、也不在 package.json。 T4-1 已把它的悬挂数降到 10 并把末尾诊断改准,但那 10 处(8 处 plan 归档路径 + 2 处正文里的省略号占位符)还在。接 CI 前必须先清干净或显式豁免, 否则是新加一道恒红闸门 —— CLAUDE.md 里已记着「一道恒红的闸门等于没有闸门」的实测教训 (2026-08-18 起 CI 连红 6 天,纯文档 commit 也挂)。做不做由 T4-2 会话判断,但要在台账写明结论。
F. 另一处恒加载文件里的过期描述(归 T4-4,此处只登记)。 红线 #10 写着「RVH 侧改名待其 /preinstalled-db-update(在此之前跨端按文件名比对会暂时对不上)」—— 实测已不成立:rvh/assets/databases/lampio_dict.db 就是新名,脚本 §B 报 ✅ lampio_dict.db byte-equal(06cbb4dc…)。cutover 已完成,那句话该删。
✅ T4-2 结果(2026-08-29)
1. 相对化。 RVH_ROOT="${RVH_ROOT:-$RB_ROOT/rvh}"(按开工前实测 C 的告诫,不是裸 rvh —— RB_ROOT 是按脚本自身位置推导的绝对路径,旁边塞 cwd 相对路径会在换目录时坏)。 连带改了三处文案:文件头用法说明、-h 帮助、目录守门失败时的提示。 实测 RVH_ROOT 出现 14 次,除定义行外全部自然跟随,一处都不用单独改。
2. §E 重做 —— 这才是 T4-2 的主体,不是「加个 workflow」。 开工前实测 A 留下的 3 个 ❌ 有个共同形状:脚本自己在 §E 抬头写着「本节差异需人工判断是否 local-only」,却用 bad() 把结论顶成退出码 1。「需人工判断」和 CI 的二元判定不可调和, 所以真正要做的不是豁免这 3 条,是把那句人工判断机械化:
「哪些列参与同步」本来就有一份真相源 ——
supabase/sql里的远端 DDL。 列在远端表里 ⇒ 同步列 ⇒ 两端都必须有(缺一端 = 那端同步不了该列 = ❌); 列不在远端表里 ⇒ local-only ⇒ 两端不同合法,只打印不判失败。
于是 3 条 ❌ 里的 4 个列(cached_file_path / content_html / linked_at / added_at) 自动转为 local-only 信息行 —— 没有名单要维护。 docs/verification/learning-loop.md 的 K11 原本开的方子是「加一张 local-only 白名单」, 没有采纳:那一栏自己就写了「白名单本身会腐烂」,而白名单不过是把远端 DDL 抄第二遍。K11 已改标 ✅。
剩下的第 3 条 reading_notes:RVH:source_platform 是真的同步列缺失: user_reading_notes 有这一列,RVH 本地表没有,push 时硬编码 'rvh'、pull 时丢弃远端值 (rvh/lib/.../sync_repository_impl.dart 自带 TODO 承认这点)。远端列 NOT NULL DEFAULT 'rb' ⇒ 不会写坏数据,代价是 RVH 端看不出条目来自哪一端。补齐要升 RVH schema、按 §9 须开 RVH 会话, 故进脚本的 COLUMN_WAIVERS 显式豁免名单(每条必须带理由与出处,且每轮打印在报告里)。 ⚠️ 豁免名单之外的任何同步列缺失照样红 —— 这是它与「整节降级成提示」的区别,已用注入反向断言过。
3. RB 列源从 live db 改成静态重建(开工前实测没提到的坑,接 CI 前必须处理)。§E 原本优先读 ~/Library/Application Support/com.lampio.dev/lampio.db,CI 里根本没有这个文件 ⇒ 本机一套列源、CI 另一套 = 两套结论,正是实测 A 结尾警告的那类「本机红 CI 绿」。 改为 schema.sql 冻结基线 + migrations.rs 里的 ALTER…ADD COLUMN。 ⚠️ 两个必踩的点:① 光有 schema.sql 不够 —— 它自 2026-07-26 冻结(红线 #11),v28+ 的增列 只在 migrations.rs 的内联 SQL 里,实测不带上就会假报「RB 缺 reading_notes.deleted_at」; ② 抽 ALTER 时必须先滤掉注释行,migrations.rs 的注释里有一条讲 Supabase 侧 ALTER 的同形句子。 静态重建对 7 张共享表逐表等于本机 live db(实测),故没有精度损失;live db 在场时脚本 额外交叉校验一次并提示「多半是 dev app 没重启」,只作信息不判失败(那是开发机状态,不是仓库状态)。
4. 退出码语义(实测 D)+ 搭车项(实测 E)。 新增 --max-skip=N:skip() 表示「本次没验」、不计入退出码,于是路径写错 → 文件找不到 → skip → 退出 0,得到一道看起来绿其实什么都没查的闸门。CI 传 --max-skip=2,基线的两条是 §A 的 supabase_url / anon_key(取自 gitignored 的 src-tauri/.env.local,CI 里必然缺)。 🔑 这条约束同时兜住了下面 §5 那个我验不了的风险:若 awk/grep 在 Linux 上行为不同、 sup_cols 解析不出列,那张表会走 skip 分支 ⇒ SKIP 超基线 ⇒ CI 红而不是静默绿。
搭车项 check-doc-links.mjs 也一并清干净接进了 ci.yml(此前既不在 CI 也不在 package.json): 10 处里 7 处补 archive/ 前缀(4 个目标文件都确实在 docs/plans/archive/)、 3 处是占位符(24-...-handoff.md 这类省略写法 + NN-*-confirmation.md 这类尚未发号的未来文档), 由脚本新增的 PLACEHOLDER_RE = /(?:\.\.\.|\/NN-)/ 排除。先到 0 再接线 —— 接一道恒红的闸门等于没有闸门(CLAUDE.md 记着 2026-08-18 CI 连红 6 天的教训)。
5. 🔴 留给下一个会话:Linux / bash 5 首跑未验。 开工前实测 A 明写「接 CI 前请在 Linux/bash5 上各跑一次」,本次做不到 —— 本机只有系统 bash 3.2,无 bash 5、无 docker。本次做的是:本机 3.2 全绿 + 逐条排查了 可疑构造(进程替换 / mktemp -u / paste -sd / grep -vxF / awk 动态正则 / ${arr[@]}), 并给 shasum 加了 sha256sum 兜底(Linux 不保证有 perl 的 shasum)。 首次 CI 跑完请对照这个期望值(本机模拟「无 .env.local + 无 live db」得到):
## 汇总:✅ 16 ❌ 0 ⚠️ 2 (退出码 0)
⚠️ url 提取失败 / ⚠️ anon_key 提取失败 ← 这 2 条就是 --max-skip=2 的基线本机(有 .env.local + 有 live db)则是 ✅ 18 ❌ 0 ⚠️ 0。两边差的正好是那 2 条 SKIP。
6. 本次跑过的反向断言(每条都真的红过): ① 往 sync-tables.sql 塞一个两端都没有的列 → ❌ 同步列缺失 + 退出码 1; ② --max-skip=1 而实际 SKIP=2 → 退出码 1(--max-skip=2 时 0); ③ RVH_ROOT=/nonexistent → 目录守门硬失败退出 1; ④ 往 backlog.md 注入一条不存在的 plan 引用 → check-doc-links 退出 1; ⑤ PLACEHOLDER_RE 宽度检查:带 archive/ 的真路径、含点号的普通文件名仍被校验,只放过带 ... 与 /NN- 的两类。 (这条自己就演示了闸门在工作 —— 初稿把两个反例当作字面路径写进本节,check-doc-links 当场报了 2 处悬挂。)
7. 顺手订正的两处过期描述(实测 B / 脚本 checklist): · 根 CLAUDE.md §9「cross-end-check.sh §D 仍是 heuristic(打印一份散文写死的期望值)…… 那份散文与 JSON 是两本账」——散文早在 2026-08-27 就删了,现从 JSON 现推。恒加载文件里的错误地图,已改。 · 脚本 checklist 第 1 条「RVH supabase/migrations/*.sql 停在 v51」——那个目录已在 T2-4 整目录删除。 已改写成「远端 DDL 从此只有 supabase/sql/sync-tables.sql 一份,仍需人工确认线上真的 ALTER 过」。 (实测 F 那条红线 #10 的过期描述未动,它按原计划归 T4-4。)
8. 没做的:脚本 §D 里 has6 是无调用的死变量(早于本次),属 T4-3 的地盘,没顺手动。
T4-3 sm2-golden-vectors.json 去重
rvh/test/fixtures/sm2-golden-vectors.json 现在是根 docs/cross-end/sm2-golden-vectors.json 的逐字节副本。改成引用同一份文件 (Dart 测试用相对路径读,或构建期 copy)。三端(Rust / TS / Dart)从此消费同一个物理文件。
⚠️ 做之前先确认 T2-1 里那个 M test/fixtures/sm2-golden-vectors.json 的改动 已经三端同步过了,别把一个未对齐的版本固化成单一真相源。
🔴 开工前实测(2026-08-29,T4-2 收尾时顺手做的只读探查)
同 T4-2 的理由记在这里:上面那段正文写于合仓前。T4-3 的真正工作量不在改 Dart 那一行, 在于它会顺手废掉两道现存守卫,不一起改就等于用「维护一份副本」换来「一道死掉的闸门」。
A. 上面那条 ⚠️ 前置条件已满足。 两份 JSON 现在字节相等 (3ec3318388d8…,2026-08-29 实测)—— T2-1 那次改动(只动 _doc / _consumers 两个说明字段) 的 cp 已经做过了。可以直接开工,不必再回头对账。
B. Dart 侧读法与 cwd。 rvh/test/sm2_golden_test.dart:19 是 File('test/fixtures/sm2-golden-vectors.json') —— cwd 相对。 flutter test 与 ci-rvh.yml(defaults.run.working-directory: rvh)的 cwd 都是 rvh/, 所以目标写成 ../docs/cross-end/sm2-golden-vectors.json 即可,CI 侧不需要额外改动 (actions/checkout 拿的是整个仓)。
C. 🔴 会废掉两道守卫,必须同批改,否则是净损失。scripts/learning-loop-verify.sh:
- L1 断言的正是「RVH 那份副本还是不是逐字节副本」。副本一删,L1 走
skip "RVH 副本不可达 —— 本次没验,不是通过"⇒ 它不会红,会安静地降级成不检查。 这正是 T4-2 刚用--max-skip堵住的那类假绿,别在这里又开一个。 - L2 的 Dart 分支是
grep -c 'sm2-golden-vectors.json' test/sm2_golden_test.dart—— 只认文件名,改了路径仍然全绿,抓不到断线。 - 两条都要重写成「Dart 消费方读的就是根那一份」的判据(路径断言 + 反向注入验一次)。 L1 在「只剩一份文件」之后其实没有对象可比了,考虑改成「不存在第二份副本」的负向断言。
D. 三处描述会随之过期,同批改:
rvh/test/fixtures/README.md整份是「副本协议」,含一条用老跨仓绝对路径的cp命令。- 根
CLAUDE.md§9 SM-2 那条写着「RVH 侧 fixture 是逐字节副本」。 scripts/cross-end-check.sh§D 末尾那句「副本漂移 →learning-loop-verify.shL1/L2」。
E. docs/verification/learning-loop.md 的 K2 就是本项的立项理由,做完要改标。 K2 写的「不修理由」是「让 RVH 跨仓读 RB 的文件会让它的 CI 依赖『RB 仓在同机并列存在』, 那个理由是成立的」—— 合仓后这个理由自动失效,两份文件现在同属一个 checkout。 按 T4-2 处理 K11 的同款做法:改标 ✅ 并写明「原因不是问题被解决了,是前提没了」。 rvh/test/fixtures/README.md 里那句「之所以复制而非跨仓绝对路径读」是同一条理由的第二处副本。
F. learning-loop-verify.sh 目前不在 CI、也不在 package.json(实测 grep 两处皆空)。 本项不负责接它进 CI(那是另一件事),但改完 L1/L2 请至少手工跑一次并把结果写进台账 —— 一道没人跑的守卫改错了不会有任何反馈。
✅ T4-3 结果(2026-08-29)
1. 主改动三行。 rvh/test/sm2_golden_test.dart 的 File('test/fixtures/sm2-golden-vectors.json') → File('../docs/cross-end/sm2-golden-vectors.json'); git rm 掉那份副本;rvh/test/fixtures/README.md 从「副本协议」改写成 「这里刻意没有这个文件」(含三端消费方表 + 别让副本长回来的理由)。 flutter test test/sm2_golden_test.dart 21 条全过。
2. 🔒 L1/L2 是本项的真正工作量,判据反转而不是废弃(开工前实测 C 说的那个净损失):
| 旧判据 | 新判据 | 不改的后果 | |
|---|---|---|---|
| L1 | 两份文件 sha 相等吗 | 负向断言:全仓(排除 .git/构建产物)不许出现第二个 sm2-golden-vectors.json | 走 [ ! -f 副本 ] 那支 → skip「本次没验」⇒ 不会红,安静降级成不检查 |
| L2 | grep -c '文件名' | 按 Dart 里那个字面量断言读的是根那一份;引用了但不是根那份 / 完全没引用,两种情形分别报 | 只认文件名 ⇒ 把路径改回读某份本地副本仍然全绿 |
L1 选「全仓找同名文件」而不是只盯 test/fixtures/:副本会长回来,但不一定长在原地 (放进 assets/、test/data/ 一样能被 Dart 读到)。
3. 🔴 四处反向注入,其中两处当场抓到我新写的守卫自己的缺陷。 注入 ①(副本长回老位置)· ②(副本长在 rvh/assets/)· ③(Dart 路径改回读本地副本)· ④(Dart 引用整条消失)——四处都必须红。第一轮跑完 ③④ 暴露出两个 bug:
_any=$(grep -c … || echo 0):grep -c无匹配时自己会打印 0 并 exit 1,再接|| echo 0得到两行"0\n0"→[ -ge ]报integer expression expected。 这个写法是从改造前的旧代码继承来的,一直没触发只是因为匹配从未落空。- 注释喂饱计数器:④ 本该报「已断线」,实际报「引用了但不是根那一份」—— 因为该文件的头注释里就写着文件名。本仓已经栽过一次同型的 (
learning-loop.md§3.3「守卫自己的假绿」)。现两处计数都先剥掉//注释行。 修完四处判定全部正确、无杂散报错。
4. 同批订正 5 处描述(开工前实测 D/E 列的三处 + 两处自己冒出来的): rvh/test/fixtures/README.md(整份重写)· 根 CLAUDE.md §9 SM-2 那条 (「RVH 侧 fixture 是逐字节副本」→ 三端同一个物理文件,并点明「改任一条 expected 三端同步红」 这句话在有副本的年代只有一半是真的)· scripts/cross-end-check.sh §D 的两处措辞 · docs/verification/learning-loop.md 的元前提二(整段重写:从「只有一半是真的」改成 「曾经只有一半、现在是全真的」,并写明 L1/L2 为何是反转而非废弃)与 §3 第 2 条 · docs/plans/backlog.md 的 K2 条目。
5. K2 按 T4-2 处理 K11 的同款做法改标 ✅,并写明 🔴 原因不是问题被解决了,是前提没了 —— 它原来的「不修理由」(RVH 的 CI 不能假设 RB 仓 在同机并列存在)在双仓时期成立,合仓后自动失效;本条原来开的方子(把 cp 接进改 JSON 那一步)没有采纳 —— 那解决的是「怎么同步得更可靠」,而现在没有第二份可同步了。
6. 搭车清掉 T4-2「没做的」里点名的死变量:cross-end-check.sh §D 的 has6 (grep -cE "(==\s*2|=== 2)" 赋了值从来没人读)。
7. 验收:learning-loop-verify.sh L 段全绿(L1 ✅ / L2 三端 ✅ / L3 ✅)· cross-end-check.sh ✅ 18 ❌ 0 ⚠️ 0 · check:claude-paths / check:doc-links 双绿 · flutter test test/sm2_golden_test.dart 21 passed。 ⚠️ 开工前实测 F 那条仍然成立:learning-loop-verify.sh 不在 CI、也不在 package.json, 本项没有改变这一点(接它进 CI 是独立一笔活)。
T4-4 红线收敛 ✅(2026-08-29)
把 #6d(明写"双端契约 RB ⇄ RVH 同文")、#5i(对端 #5h)、#9、#10 四条 收敛成单一定义 + 两端各自的落地形状。这是 T3-8 的深化:T3-8 只做到"rvh 侧改为引用", T4-4 要做到"两端守卫共享同一份判据表述"。
做完后按 CLAUDE.md §4「红线归属规则」核对:这四条的归属都在根,rvh/CLAUDE.md 只留指针。
✅ T4-4 结果(2026-08-29)
1. 四条改成四段式。 根 CLAUDE.md §4 的「双端契约红线正文」此前是一张 4 行表, 单元格分别 1705 / 3468 / 541 / 2427 字符 —— 规则、事故记录、RB 实现、RVH 现状全搅在一格里, 于是「改规则」和「改某一端的实现」在文本上无法区分,这正是两端叙述会漂的机制。 现固定为 规则(两端同文)→ 为什么 → RB 落地 → RVH 落地 四段, 并在节首写死「改规则 → 改这里;改某一端落地 → 改那一端的文件」。 迁移用 52 条关键短语做了机械核对(grep -F 逐条),0 条丢失。
2. 🔴「统一编号」改判为不统一 —— 与 T4-1 同款判据。rvh/CLAUDE.md 的编号对照表此前写着「收敛成单一编号是 T4-4 的活」,本次否决: ① 实测 rvh/ 内约 350 处编号引用(lib/test 注释 130+ 处 + docs/cross-end/ 冻结 handoff + CHANGELOG + SQL 注释),重编号 = 在 §0 禁改的历史记述里造死链; ② 两套编号已经在混用且工作正常 —— 凡 RVH 没有本地编号的条目,其 Dart 注释直接写 RB 的号 (实测 红线 #7 / #9 / #6a / #1 都在场),真正会出事的只有 6x 段那几个同号不同义的; ③ 重编号没有任何机械守卫(没有 lint 能验一个裸 #6d 指的是哪个仓)。 替代 = 双向指针:根侧每条写明对端编号,rvh/CLAUDE.md 对照表反向写明根侧位置, 并补了两行此前缺的(RB #9 / RB #7 这类「本仓无编号、注释直接引 RB 号」的情形)。
3. 删掉 #10 的过期 cutover 句(T4-2 开工前实测 F 登记的那条): 「RVH 侧改名待其 /preinstalled-db-update(在此之前跨端按文件名比对会暂时对不上)」—— 两端早已都是 lampio_dict.db 且 byte-equal。#10 的历史沿革(2026-06-15 折叠 / 07-19 pipeline 反转 / 07-26 改名)从正文段落改成一张 3 行沿革表,规则本身只剩 3 句。 顺带删掉 pipeline config 路径那句 —— 它的权威源是 tools/vocabulary_builder_v3/config.yaml 自己。
4. #9 补了「键空间细则」这一层。 此前 §4 #9 只写「必经 normalize()」, 而 2026-08-28 cross-end/43 已把它 细化成「解析成一个真实存在的 vocabulary 行」(因为 vocabulary 里同时住着 lemma 与非 lemma 行)—— 那条细则此前在恒加载文件里完全没有痕迹,只活在 word_key.rs 头注释与 RVH 侧。 现在 §4 #9 写明它是两端同义的细则、规则本体在实现文件头注释(不复述第三份), 并补上 RB 侧多一条 surface 腿、RVH 侧当前不需要分流的差异。
5. 顺带:把「RVH 未对齐红线」的三本账收成一本。 同一份名单当时存在三处,且两处是错的:rvh/CLAUDE.md §跨端契约镜像 backlog(正确:#2 · #6, #7 / #9 已于 2026-08-28 修完,实测测试文件与 word_key.dart 都在)· scripts/cross-end-check.sh 人工 checklist 第 3 条(写死「自曝 4 处」)· docs/verification/learning-loop.md K7(写着 #7 / #9 仍在)。 现定死唯一真相源 = rvh/CLAUDE.md 那节,另两处改为指针;K7 改标 ✅ 并把「三本账」这件事本身记进去。 残留两条也复核过:#2 = rvh/lib 里 33 处 LOWER(;#6 = clearLearningDataForUser 仍 hard DELETE 5 张表。
6. 没做的:rvh/CLAUDE.md 的 11 个 grep 守卫块仍然没有任何东西在执行(§7 那条待决项)。 把它们接进 ci-rvh.yml 是独立的一笔活 —— 本项只动叙述归属,不动执行。
T4-5 65MB 预装库去重
src-tauri/assets/lampio_dict.db 与 rvh/assets/databases/lampio_dict.db SHA 完全相同(红线 #10 要求 byte-equal),各 65,777,664 B。
理想形态:后者做成 symlink 指向前者(git 原生支持 symlink)。收益: 每次 reseed 少长 65MB;红线 #10 从"靠脚本断言"变成"结构上不可能违反"。
必须先实测(Flutter 对 symlink 资产的处理未知): pubspec.yaml 的 assets 路径必须在 package 内 / flutter build 是否跟随 symlink / Android 与 iOS 打包是否都可以。
退路:symlink 不行就改 build 步骤 copy——工作区仍两份,但 git 里只有一份, 主要收益(历史体积 + 红线机械化)仍然拿到。
⏭ 掉也是合法结果(写进台账):这一项不做,合并的其他收益一分不少。
🔴 开工前实测(2026-08-29,T4-8 收尾时顺手做的只读探查)
上面那段正文写于合仓前。T4-5 真正的难点不是 symlink 能不能用,是它会把一道 现役闸门变成恒真的同义反复 —— 与 T4-3 的 L1 完全同型,处理方式可以照抄。
A. 前置状态复核。 两份仍 byte-equal:各 65,777,664 B,sha256 同为 06cbb4dcde22…(实测)。
B. 🔴 symlink 之后 cross-end-check.sh §B 会变成同义反复。 §B 现在是 [ -f "$RB_VOCAB" ] && [ -f "$RVH_VOCAB" ] 后各算一次 sha256 再比。 [ -f ] 与 sha256 都跟随 symlink ⇒ 做成 symlink 后它是拿同一个文件跟自己比, 永远相等、永远绿、再也发现不了任何东西。这不是 bug,是判据的对象没了: 「两份文件一不一致」在只剩一份之后不成立。
- 正确做法与 T4-3 的 L1 同款:改成结构断言 ——「RVH 那个路径确实是指向 RB 那份的 symlink」 (
[ -L ]+readlink目标解析到src-tauri/assets/lampio_dict.db), 并保留一条负向断言:它不许是一个独立的普通文件。 - ⚠️ 改完必须反向注入验一次(把 symlink 换回真文件副本 → §B 必须红)。 只改不注入 = 又造一道自己不知道瞎没瞎的闸门。
- 走**退路(build 期 copy)**时同样要改:那时工作区有两份,但 git 里只有一份, §B 比的是工作区 ⇒ 判据要变成「git 里只有一份 + 工作区那份是构建产物」。
C. CI 会真的跑到它。 ci-cross-end.yml 的 paths 含 rvh/** 与 src-tauri/assets/lampio_dict.db,所以本项的改动会触发它;ci-rvh.yml 也含 rvh/**, 且它 2026-08-29 起是绿的(T4-10),flutter test 1359 条会真的跑。 🔴 但 CI 验不了本项最关键的那条:四个 job 都不构建 native / 不打包 APK ⇒ 「Flutter 打包是否跟随 symlink」只能在本机验。
D. 本机构建 APK 的前置(2026-08-29 / T4-10 之后变了):需要 rvh/pubspec_overrides.yaml + rvh/local_packages/opencv_dart,补齐步骤见 rvh/QUICKSTART.md 开头第二节。装了之后 rvh/pubspec.lock 会常驻 modified (opencv_dart 那条 hosted→path),那一行别提交。
E. 一条正文没提、但可能致命的验证项:Windows。 git 的 symlink 在 core.symlinks=false 的 Windows clone 上会变成一个装着目标路径的文本文件。 RVH 只发 iOS/Android,但整个仓会被 clone 到 Windows 上(桌面端要发 Windows 版)。 届时 rvh/assets/databases/lampio_dict.db 会是一个几十字节的文本文件, 而 §B 改成结构断言后在 Windows 上会红(或更糟:改得不好会绿)。 这条本次未验(本机是 macOS)—— 开工时先想清楚它,别等 Windows 发版时才撞上。
F. .gitattributes 有两行相关:src-tauri/assets/lampio_dict.db binary 与 rvh/assets/databases/*.db binary(T3-4 加的)。symlink 之后第二行指向的东西不再是 blob, 顺手确认它不会产生奇怪的 diff 行为。
✅ T4-5 结果(2026-08-29)
做法 = symlink(用户裁定;退路 build-期-copy 被排除,理由见下面 §2)。
rvh/assets/databases/lampio_dict.db → ../../../src-tauri/assets/lampio_dict.db(相对 symlink, git 存为 mode 120000 的 40 字节 blob)。
1. Flutter 侧实测:能用,无任何适配。
| 验的什么 | 结果 |
|---|---|
flutter build bundle --target-platform=android-arm64 | 成功;build/flutter_assets/assets/databases/lampio_dict.db 是真实的 65,777,664 B SQLite 文件,sha256 06cbb4dc… 与源一致 |
AssetManifest.bin | 含该条目 |
flutter test | 1359 passed / 5 skipped,与 T4-10 基线逐字相同 |
flutter analyze --no-fatal-infos | exit 0 |
| 运行时读法 | rootBundle.load('assets/databases/lampio_dict.db')(app_database.dart / lemmatizer_flutter_loader.dart)—— 读的是打包后那份,与 symlink 无关 |
⚠️ 首次 flutter build bundle 会因默认 target 是 android-arm 而失败(third_party/sqlite3/ 只 vendored 了 arm64-android / x64-macos / x64-linux 三个,没有 arm)—— 与 symlink 无关, 是 T4-10 死因⑥ 的同一处。要跑就带 --target-platform=android-arm64。 iOS 未单独跑 Xcode 构建;资产拷贝走的是同一个 BundleBuilder,平台差异只在 native snapshot。
2. 🔴 正文的收益模型被实测推翻:「每次 reseed 少长 65MB」不成立。
git 按内容寻址 —— byte-equal 的两份天然只存一个 blob:
| 判据 | 实测 |
|---|---|
| HEAD 两个路径的 blob SHA | 同一个 b7c3bdd4 |
| 全历史 distinct blob:RB 路径 / RVH 路径 / 并集 | 7 / 6 / 8(交集 5,不是 13) |
⇒ 历史体积收益 ≈ 0,clone 下载量也不变。symlink 真正换来的只有两条: 工作区 checkout 少落 65 MB,以及红线 #10 从「靠断言」变成「结构上不可能违反」。 这也是退路(build 期 copy)被排除的理由:它唯一的卖点「git 里只有一份」被上面这张表证伪, 却要新增一个「构建前必须先 copy」的隐式前置(flutter test / CI / 干净 clone 都得挂上,忘一处就是资产缺失)。
另一条值得记的:红线 #10 在做本项之前就已经有机械守卫了(T4-2 把 §B 接进了
ci-cross-end.yml)。所以本项的边际价值是「从有闸门 → 不需要闸门」,不是「从没守 → 有守」。 ⏭ 掉本项当时同样是合法选择。
3. cross-end-check.sh §B 判据换型(正文「开工前实测 B」点名的那件事)。
旧判据 [ -f ] × 2 + sha256 比对 在只剩一份之后是拿同一个文件跟自己比 —— 恒绿 ([ -f ] 与 sha256 都跟随 symlink)。改为结构三态:
| 态 | 判据 | 含义 |
|---|---|---|
| ✅ | [ -L ] 且 [ "$RVH_VOCAB" -ef "$RB_VOCAB" ] | 只有一份物理文件 |
| ⚠️ | 普通文件、≤512 B、内容 == 链接目标字符串 | Windows core.symlinks=false 的检出形态,判「本次没验」 |
| ❌ | 其余(真 db 副本 / 硬链接 / 指错地方 / 悬挂) | 有人把 symlink 换回了两份 |
用 -ef(同设备同 inode)而不是比 readlink 出来的路径字符串:字符串要先归一(.. / 中间符号链) 才能比,而 realpath / readlink -f 在 macOS 上不保证在场。-L 不能省 —— 单用 -ef 的话 硬链接也会过,而硬链接在 git 里存的仍是两个普通文件。
🔒 四种缺陷各反向注入实测过(正文明令「只改不注入 = 又造一道自己不知道瞎没瞎的闸门」): ① 换回真 db 副本 → ❌ · ② 40 字节文本占位 → ⚠️ · ③ symlink 指到 nlp/base_forms.json → ❌ · ④ 硬链接冒充 → ❌(这条是写判据时顺手补的,正文没提)。复原后 → ✅。 全脚本 ✅ 18 ❌ 0 ⚠️ 0 退出码 0;CI 的 --max-skip=2 基线不受影响(那 2 条在 §A)。
4. 🔴 Windows:用户裁定「接受 + 做成可观测」。
git 在 core.symlinks=false 的 clone 上把 symlink 落成装着目标路径的 40 字节文本文件, 而本仓确实会被 clone 到 Windows(ci-rust.yml / release.yml 都 runs-on: windows-latest)。 当前破坏面为零:那两条 workflow 只编译 src-tauri/,RVH 不在 Windows 上构建; ci-cross-end.yml 跑 ubuntu,不会因此误红。风险是未来的(有人在 Windows 上开发 RVH), 故做成上面 §3 的 ⚠️ 那一支 —— 它与 ❌ 分得干净(占位是 40 字节路径文本,不是 65MB 的 db 副本)。 补齐办法写进了 rvh/QUICKSTART.md 开头那张「缺什么」表:git config core.symlinks true 后重新 clone。
5. 同批改掉的文档(grep byte-equal 命中很多,只改了「规则/机制」处,没去动历史记述)。
| 文件 | 改了什么 |
|---|---|
CLAUDE.md §4 红线 #10 | 标题 + 索引行 + 规则正文(「两端字节相等」→「双端只有一份物理文件」)+ 机械守卫段(换成三态 + Windows 段)+ 沿革加一行;另订正 §2 仓库地图、§4 #9、§5 缓冲池三处顺带提到 byte-equal 的句子 |
src-tauri/CLAUDE.md §3 | 预装词库那行改为「双端共用的那一个物理文件」 |
rvh/CLAUDE.md | #5e 拆成两类资产(🔴 预装库 = symlink 单点、无接收/校验动作;assets/nlp/*.json 仍是真两份、byte-equal 照旧靠 §C 断言)+ 编号对照表那行 + 危险操作清单里「删预装库」那行 |
.gitattributes | rvh/assets/databases/*.db binary 保留但注释改写(对 mode 120000 是 no-op) |
rvh/QUICKSTART.md | 「缺什么」表加 Windows 那行 |
⏭ 刻意没动:docs/cross-end/* / docs/plans/archive/* / CHANGELOG.md 里的 byte-equal (§0 禁止事项第四条:历史记述)。红线 #10 正文里已写明「读老文档遇到 byte-equal = 同一条契约的旧形式」。 另外没动那个 sync 脚本及其 5+ 处引用 —— 那是 T4-6(本项只从红线 #10 正文里摘掉了「跑完必须立刻 sync 整包同步」这句已经不成立的收尾要求)。
验收:cross-end-check.sh ✅18/❌0 · learning-loop-verify.sh L1-L5 全绿(M1/M3 两条 FAIL 是既有的 live-data 发现:裸时间戳 + 8h 时区偏移,与本项无关)· check:claude-paths ✅ · check-doc-links ✅ · flutter analyze exit 0 · flutter test 1359 passed。
T4-6 删 / 简化跨仓脚本
| 脚本 | 处置 |
|---|---|
scripts/sync-rvh-vocabulary.sh(124 行) | T4-5 做成后可删;否则改成仓内 copy |
scripts/verify-rvh-alignment.sh(337 行) | 大幅简化——很多断言在同仓下变成恒真 |
🔴 开工前实测(2026-08-29)
A. 🔴 sync-rvh-vocabulary.sh 里装着两样删掉就没有替代品的东西,删它之前必须先给它们找到新家:
- 体积闸:
≥80 MB 告警 / ≥100 MB 判死并拒绝 cp。它挡的是 GitHub 单文件 100 MB 硬限 (超了 push 直接被拒)。当前资产65,777,664 B,轨迹 8→52.7→62.7 MB, 按/vocab-reseedskill 的估计只剩约 2–4 轮大扩容。删脚本 = 顺手关掉这道闸, 而它要挡的事故是「push 被拒、整轮 reseed 卡死」。 - 资产卫生断言:4 表白名单 +
lemma_*行数(140370/101646)+ 「audio_local_path/last_accessed_at全 NULL(无机器污染)」。/vocab-reseed的 SKILL.md 明写「部署前交给脚本即可」—— 这是它们唯一的执行点。
B. 引用点比正文那张表多得多(改脚本要同批改,grep -rn 'sync-rvh-vocabulary'):
- 🔒
CLAUDE.md§4 红线 #10 正文两处(「发版用sync-rvh-vocabulary.sh整包覆盖」+ rebake 那条豁免的收尾要求)—— 这是改红线正文,按 §4「红线归属规则」办,别在别处补第二份。 .claude/skills/vocab-reseed/SKILL.md5 处(含上面 A 那两条的说明)。tools/README.md·tools/vocabulary_builder_v3/README.md(2 处,含一条可复制的命令)。.claude/settings.local.json里有verify-rvh-alignment.sh的三条授权项(删脚本后是死条目)。
C. verify-rvh-alignment.sh 简化时别把它的职能整个删掉。.claude/skills/cross-end-check/SKILL.md 两处明写:它验的是同步数据行, 而 /cross-end-check 验的是结构(schema / 常量 / 资产),两者不重叠。 「同仓后很多断言变恒真」成立的是路径与可达性那部分,不是数据行断言。
D. 依赖 T4-5 的结论:sync-rvh-vocabulary.sh 是「删」还是「改成仓内 copy」, 取决于 T4-5 选了 symlink 还是退路。先做 T4-5。 若 T4-5 被 ⏭ 掉,本项就只剩「把跨仓 cp 改成仓内 cp」+ 简化 verify 脚本。
✅ T4-6 结果(2026-08-29)
🔴 两项都改判。正文那张表的两格处置各错一半,但错的方向相反。
① sync-rvh-vocabulary.sh:不是「删」,是「改型」。
正文写「T4-5 做成后可删」,但开工前实测 A 已经指出它装着两样没有替代品的东西。 T4-5 选了 symlink ⇒ 它的 cp 确实没有对象了,可那两道闸与 cp 无关:
| 留下的 | 它挡什么 |
|---|---|
| 体积闸(≥80 MB 告警 / ≥100 MB 退出 1) | GitHub 单文件硬限 100 MB —— 超了 push 直接被拒,双端共用它 ⇒ 分发链路断。当前 62 MB,轨迹 8→52.7→62.7,约剩 2–4 轮大扩容 |
| 资产卫生断言 | 4 表白名单 + lemma_* 行数(140277 / 101713)+ audio_local_path / last_accessed_at 全 NULL(无机器污染)。/vocab-reseed SKILL.md 明写「部署前交给脚本即可」—— 这是它们唯一的执行点 |
⇒ git mv scripts/sync-rvh-vocabulary.sh scripts/check-vocab-asset.sh(改名保 blame), 删掉 cp / DST / 幂等比对,留下上面两道闸 + SHA 打印 + 两条 "Next" 提醒, 另加一条只做在场性检查的 symlink 提示(判据本体在 cross-end-check.sh §B,这里不写第二本账)。
🔴 顺带补一条正文没意识到的:脚本原先「必跑」是因为你必须跑它才能把库送到 RVH。 symlink 之后那个必要性消失 ⇒ 闸门退化成「有人想起来才跑」—— 换名字不换命运,等于慢性地把闸关掉。所以同批把它接进 .github/workflows/ci-cross-end.yml(该 workflow 的 paths 本来就含 src-tauri/assets/lampio_dict.db),换一条人忘不掉的必经之路。 ⚠️ 但CI 挡不住体积闸真正要挡的事故:≥100 MB 时 push 本身被 GitHub 拒,而 CI 在 push 之后跑 ⇒ /vocab-reseed Step 3 提交前那次人工执行仍是主闸,两处都写明了这一点。
② verify-rvh-alignment.sh:一行没改,「大幅简化」的前提不成立。
正文(与 rvh-merge-evaluation.md 那张表)写「337 行 · 大幅简化 —— 很多断言在同仓下变成恒真」。 实测全文对 RVH 的引用只有 Stage 4 打印给人看的那份手工清单:
grep -n 'reading_vocab_helper\|RVH_ROOT\|rvh/' scripts/verify-rvh-alignment.sh
→ 唯一命中是清单里的一行文案「□ flutter analyze / dart test 无涉及 refwords 的残留代码」零跨仓路径、零 RVH 可达性断言。 它验的全是 RB 本地 SQLite + Supabase 远端 REST/Storage, 合仓改变不了其中任何一条 ⇒ 没有任何断言变成恒真,没什么可简化。 (真正因为「默认 RVH_ROOT 指老仓、一直在验错对象」而需要修的是 cross-end-check.sh —— T4-2 已修。 两个脚本在评估里被并列写进同一张表,但只有一个真的跨仓。)
它现在的真实状态:一次性阶段验收(阶段 3-5 storage_path ✅ / 3-6 reference_words 未闭 —— user_reference_words 待用户在 Dashboard DROP TABLE ... CASCADE,根 CLAUDE.md §5 也这么记着)。 3-6 闭掉之前它是那件事唯一的自动检查,删了就没人验了 ⇒ 保留,只在文件头补了这段复核结论。 .claude/settings.local.json 里那三条授权项因此不是死条目(正文的推测基于「会删」)。
③ 同批改掉的引用(正文实测 B 列了 5 处,实际动了 8 个文件):
| 文件 | 改了什么 |
|---|---|
CLAUDE.md 红线 #10 | 补 RB 落地 段:闸门新家 = check-vocab-asset.sh,两道闸各是什么、谁跑它、CI 为什么不够;沿革加 T4-6 一行(含「读老文档遇到『跑 sync 脚本整包覆盖』= 今天的什么」换算) |
.claude/skills/vocab-reseed/SKILL.md | 5 处 → 一句话定位改写 + Step 3 从「部署 + 反向 sync」改成「部署 + 过闸门」+ 明写「RVH 侧没有动作」;顺带订正那行写死的过期 lemma 行数(140370/101646 → 改为「以脚本为准」) |
tools/README.md | 数据流图末端从 ~/reading_vocab_helper/...(合仓后就没跟着改)改成 rvh/... + symlink |
tools/vocabulary_builder_v3/README.md | 2 处(含一条可复制的命令) |
rvh/.claude/skills/preinstalled-db-update/SKILL.md | 🔴 正文没列到 —— 它整个 Step 1「接收 RB 产物 + byte-equal 校验」已经没有对象了。改写定位 / 数据流图 / 两库两操作表 / Step 1,version 4.0.0 → 5.0.0。✅ 同日补完全文审计(v5.1.0):reading_vocab.db→lampio_dict.db · notebook_entries→learning_entries · 设备活库名也错着(reading_vocab.db→lampio.db,与预装 asset 是两个文件)· 版本锚散文写死 =25 而实际是 29 ⇒ 改成「只看代码」· 双仓绝对路径全部仓根相对 |
docs/plans/backlog.md | 3 处:LFS 那条的假绿论证(其中「两边都是 pointer 时报 byte-equal ✅」这条具体路径已随 §B 换型消失,但打包塞 pointer 那条仍然没人挡)· 体积闸「正确的家」那段补换家记录 · 短语 B 接力条里两个过期行数 |
docs/plans/proper-noun-governance-plan.md | rebake 豁免的收尾要求 |
.github/workflows/ci-cross-end.yml | 新增 Preinstalled vocab asset gate step |
⏭ 刻意没动:docs/cross-end/* · docs/plans/archive/* · CHANGELOG.md · rvh/CHANGELOG.md 里的 sync-rvh-vocabulary.sh(§0 禁止事项第四条:历史记述),红线 #10 沿革里已给了换算。 docs/plans/rename-plan.md 那处也是「RB 已做」的历史记述,同理不动。 rvh/docs/plans/* 两处属 RVH 侧的历史计划,留给 RVH 会话。
验收:check-vocab-asset.sh 退出 0(体积 62 MB / 4 表白名单 / 140277·101713 / 污染 0 / symlink 在场)· cross-end-check.sh ✅18 ❌0 ⚠️0 · bash -n 两个脚本 · check:claude-paths ✅ · check-doc-links ✅ · ci-cross-end.yml 四个 step 结构正确。
T4-7 删 CLAUDE.md §9「跨仓库路径约定」 ✅(2026-08-29)
整节删除(那条规则的存在前提就是两个仓)。同时把 §9 其余部分改写为 「双端整合」而非「跨仓协调」。
✅ T4-7 结果(2026-08-29)
1. 整节删了,换成 3 行「路径写法(合仓后)」。 留下的不是新规则,是读老文档时的换算: docs/cross-end/* 与 docs/plans/archive/* 里仍有大量 ~/reading_vocab_helper/..., 它们等价于今天的 rvh/...,但属历史记述不要去改;check-doc-links.mjs 对它的负向断言 因此保留 —— 挡的不是「违规写法」,是「别把老路径误报成悬挂」。
2. 🔴 改写 §9 其余部分时,查出它在说三处假话(这才是本项的实际价值,删那节只占 ~0.7KB):
| 假话 | 真相 | 后果 |
|---|---|---|
「RVH 位于 ~/reading_vocab_helper」 | 2026-08-28 已是本仓 rvh/ | 恒加载文件里的错误地图 |
「RVH 待镜像表改名(table-rename-rvh-handoff.md)」 | RVH 2026-07-09 以 schema v56 镜像完成、07-10 三端 verified(docs/archive/09-*.md 与 rvh/CHANGELOG.md 都记着) | 错了七周。而那份 handoff 自己也停在「⬜ 阻塞于 RVH 会话」—— 两份文件互相印证了一个不存在的状态 |
「跨仓路径 check-claude-md-paths.mjs 检查不到,只能靠人核」 | 合仓 + T3-7 之后它能查(rvh 已进 ROOTS 与命令行清单) | 这条注解就写在 SM-2 那个「错了一个月的路径」旁边,等于劝人别指望机械守卫 |
3. 两份改名计划标 ✅ 并归档(它们自己写着「RVH 收尾后一并归档」,迟了七周)。 归档后 5 处指针失效,一并改成 docs/plans/archive/...:rvh/CHANGELOG.md · rvh/docs/database/schema.md · rvh/assets/sql/{01_create_tables,03_init_data}.sql · rvh/lib/shared/data/database/app_database.dart(纯 /// 注释行)。 归档件顶部写明了状态行为何错七周:交接单的状态必须由收尾方回填,没人回填就等于留了一张错误地图。
4. 会话隔离规则收窄为「按工具链判,不按目录判」。 原表三条理由里「文件路径冲突(两个项目有同名概念)」已随合仓消失(路径带 rvh/ 前缀), 标了删除线保留记录;技术栈与工具链两条仍成立且是主要理由。 新增「实践边界」:改 rvh/CLAUDE.md / rvh/docs/** 这类不跑 Flutter 就能验收的, 在 RB 会话做是安全的(T4-4 实测走通);要跑 flutter analyze / flutter test 的(T4-3 / T4-5)回新会话。 唯一例外是 Dart 文件里纯注释的路径修正 —— 本项自己做了一次,写进规则里免得下次自相矛盾。
5. 顺带订正的两处外围:docs/plans/docs-governance-plan.md 的 P0.6 (「CLAUDE.md 自己违反 §9 跨仓库路径约定」)标 ⏭ —— 它当时要求的改法今天是反的 (那份回执现在就在本仓 docs/cross-end/,裸相对路径才对); check-doc-links.mjs 的注释不再引用已删除的那一节。
6. ⚠️ 净增 3.4KB(8874 → 12277 B),一项以「删」为名的任务反而变大。 删的只有那 3 条约定(~0.7KB),其余是上面三处订正 + 收窄后的会话规则 + Schema 协议里 「5 张共享表」改成正确的「10 张同步矩阵 / 与 RVH 共享 6 张」+ sync.rs 改成 commands/sync/。 已经压过一轮(把「状态错七周」的复盘从恒加载文件搬进归档件)。不建议为了数字再压 —— 剩下的每一段都在改变行为或纠正事实。
7. 没做的:docs-governance-plan.md 整份看起来也部分过期(P0.1/P0.3/P0.5 描述的问题多数已修但没标), 本项只动了 P0.6 那条(它的前提被 T4-7 直接推翻)。整份审计不在范围内,登记在此。
T4-8 合并 docs/product.md
RB 那份 7.1KB 讲 Lampio;rvh/docs/product.md 14.6KB 还在讲 「reading_vocab_helper(阅读词汇助手)—— 基于 CEFR 智能过滤的阅读场景词汇习得工具」, 与 CLAUDE.md §1 明写的「竞品是浏览器/Kindle 阅读器,不是 Duolingo 等教育产品」直接冲突, 且仍在用改名前的产品名。
合成一份,移动端特有的功能描述作为其中一节。
✅ T4-8 结果(2026-08-29)
1. 立项理由说轻了:它不只是「过期定位」,是在说三条与当前产品相反的话。
| 它写着 | 事实 |
|---|---|
| 「❌ 云端同步(隐私优先)」「所有数据本地存储,不依赖网络」,并把云端同步列为 P3 暂不考虑 / v2.0.0「可能功能」 | 跨端同步是这个产品的骨架(Supabase Auth + 与桌面端共享 6 张同步表)。这条比改名严重得多 |
| 目标用户是「备考学生(托福/雅思/GRE)… 通过考试」 | 与 CLAUDE.md §1「竞品不是 Duolingo 等教育产品」直接冲突 —— 照它做需求会一路做成我们明确拒绝的东西 |
| 「❌ 多语言界面(初期专注中英双语)」 | rvh/lib/l10n/ 实装了中 / 英 / 日三种 |
同一条假话还有第三处副本:rvh/README.md 的核心功能第 5 条「🔒 隐私优先 - 本地存储, 数据不上传云端」,已一并改掉。
2. 🔴 同批查出根侧自己也有两处假话 —— 别只审对端那半边。
- 「预装词库 12,291 条」:实测
18916 = 12172 单词 + 6744 短语,12,291 在任何口径下都不对。 按 2026-08-28 backlog 那条口径裁定处理:这里根本不该写绝对数,改为指向VOCABULARY_SEED_VERSION+ 附可复跑 SQL(与当时改CLAUDE.md的做法同款)。 ⚠️ 那次裁定只改了CLAUDE.md,没人想到docs/product.md里还躺着同一个数 —— backlog 那条「剩下两件」之一(通知 RVH 镜像措辞)仍未做,rvh/CLAUDE.md两处 绝对数照旧,不在本项范围。 - 「5 表同步」:真值是同步矩阵 10 张(与移动端共享 6 张)。
3. 合并形状:根 docs/product.md 新增「移动端功能清单」14 行(逐条对着 rvh/lib/features/ 与调用方核过,不是从旧文档抄的),外加一张「移动端的规划 / as-built 去哪看」指针表 —— 旧文档里那套 FEAT 完成记录、版本规划、里程碑、成功指标不并入, 它们分别是 rvh/CHANGELOG.md / roadmap / backlog 的地盘(第二本账的成因)。 双端对比表顺手把 ReadBrowser / RVH 改成桌面端 / 移动端(改名已一年多)。
4. 为什么留指针而不是删:实测 37 处引用、分布在 8 个文件 (rvh/README.md · QUICKSTART.md · CLAUDE.md · docs/README.md · docs/architecture.md · docs/guides/doc-maintenance-guide.md · .claude/skills/doc-sync-check/SKILL.md · docs/plans/backlog.md)。删掉是造一批死链。指针文件里不留任何产品论断 —— 一旦写回产品内容它就重新变成第二本账,上面那三条假话正是这么来的。 ⚠️ 起草时我先按「9 处」写进了两份文件,实测才发现是 37 处 / 8 个文件;已改成列文件不写行数 (文件数比行数抗腐蚀)。
5. 没做的(登记,不顺手扩大范围):rvh/docs/architecture.md · docs/README.md · docs/guides/doc-maintenance-guide.md · .claude/skills/doc-sync-check/SKILL.md 里仍把 product.md 描述成「产品功能权威文档 / 每版本更新」,且那几份本身带着别的过期快照 (如 architecture.md 给 product.md 标「28」)。只改了两处最要紧的描述: rvh/CLAUDE.md 核心文档表(恒加载)与 rvh/QUICKSTART.md。整套 RVH 文档体系体检是独立一笔活。
T4-9 老仓归档
用户操作:gitee ttfishnet/reading_vocab_helper 转归档/只读,停止往它推。 只停推,不删——它是 pre-rb-merge tag 的宿主,也是最后的回滚源。
⚠️ 归档前先确认没有脚本还指着它。2026-08-29 查出两个:learning-loop-verify.sh (默认 RVH_ROOT 指老仓,一直在验错对象)与 sync-rvh-vocabulary.sh(目标指老仓, 照旧跑会静默破坏红线 #10)。两个已修(commit de68d0d),但归档时值得再扫一遍: grep -rn 'reading_vocab_helper' scripts/ .claude/ *.md —— 命中里只应剩下 「历史文档换算」性质的说明。
🟡 T4-9 预扫描结果(2026-08-29)—— gitee 那一步待用户操作
归档动作本身在用户侧(gitee ttfishnet/reading_vocab_helper → 归档 / 只读)。 本节记的是正文要求的归档前机械前置:grep -rn 'reading_vocab_helper' scripts/ .claude/ *.md, 「命中里只应剩下历史文档换算性质的说明」。
A. 🔴 正文给的扫描范围与命令都不够,会漏掉真问题。
- 范围太窄:
scripts/ .claude/ *.md三处全是干净的(那三个脚本里的命中正是 T4-2/T4-6 留的修复记录与负向断言)。 真正指着老仓的东西全在范围外:supabase/functions/**/README.md·tools/vocabulary_builder_v3/**(含.dart/.yaml)·admin/CLAUDE.md·docs/verification/**·rvh/docs/**。 - 模式太宽:裸
reading_vocab_helper会把 Flutter 包名全部捞进来 ——rvh/lib/**每个文件都有import 'package:reading_vocab_helper/...',Android 包名是com.nikos.reading_vocab_helper。首扫命中 200+ 文件,绝大多数是噪声。 判据得收成路径形态:grep -rnE '(~|/Users/[^/]+|\$HOME)/reading_vocab_helper'。 另外rvh/local_packages/opencv_dart/(gitignored 的 CMake 构建缓存,不在 git 里)会贡献 上千行命中,扫的时候要排掉。
B. 修掉的 20 处(判据 = 归档后会把人带到已归档仓,或控制文件在陈述假事实;纯历史记述一律不动):
| 文件 | 处 | 为什么必须改 |
|---|---|---|
rvh/docs/deployment/edge-function-deployment-guide.md | 3 | 可复制的部署命令 cd /Users/larry/reading_vocab_helper —— 照抄就在归档仓里 supabase link / functions deploy |
supabase/functions/lookup-or-fetch-word/README.md | 1 | 同上,一条可复制命令 |
tools/vocabulary_builder_v3/(config.yaml 2 · 4 个 bin/*.dart · lib/quality_governor.dart) | 6 | 指向 ~/reading_vocab_helper/docs/plans/ 的三份计划 —— 三份都在本仓 rvh/docs/plans/,改成仓内相对后是活链接 |
admin/CLAUDE.md | 4 | 🔴 控制文件说假话:仓库地图写「RVH ~/reading_vocab_helper」+「RVH 仍独立仓」+「RVH 在独立仓,需新会话镜像」。与 T4-7 在根 §9 查出的三处假话同型 —— 别只审自己那半边 |
docs/vocabulary-domain-knowledge.md | 1 | 「这条流水线物理上住在 RVH 仓库」—— 过期两轮(2026-07-19 迁进 RB、2026-08-28 合仓),还顺带教人「改 pipeline 必须在 RVH 新会话」,而它是纯 dart run、RB 会话就能改 |
docs/plans/backlog.md | 7 | 两份 RVH 开场 prompt 模板仍写「用 ~-锚定绝对路径读,禁止裸相对路径(CLAUDE.md §9)」—— 那条规则 T4-7 整节删了;.mcp.json cross-reference 指引要人去编辑老仓那份(T3-4 已删),而根 .mcp.json 早就两个条目都有 ⇒ 整个步骤作废 |
rvh/docs/README.md · rvh/docs/guides/README.md | 3 | skill 目录指向老仓绝对路径(顺带订正「7 个」→ 实际 8 个) |
docs/verification/cross-end-cloze-pool-2026-08.md | 3 | 🔒 常驻验证清单(永不归档),却带着 file:///Users/larry/reading_vocab_helper/... 链接与「RVH 侧前置回执」的老仓路径 —— T4-1 归并后那两份就在本仓 docs/cross-end/ |
C. 复扫结果:干净。 收紧后的模式再扫一遍,剩余命中全部是历史记述或换算说明: CHANGELOG.md · docs/archive/** · docs/plans/archive/** · docs/plans/backlog-archive.md · 本计划与 rvh-merge-evaluation.md 自身 · rename-plan.md 的 RVH 回执 · tools/vocabulary_builder_v3/README.md 的「原在 ~/reading_vocab_helper/...」· 根 CLAUDE.md §9 那条换算说明本身 · 三个脚本里的修复记录 / 负向断言。这些一处都别动(§0 禁止事项第四条)。
⚠️ 唯二例外,都不进 git、也不影响归档:.claude/settings.local.json 与 admin/.claude/settings.local.json 里各有若干条指向老仓的 Bash 授权项(gitignored 的本机文件)。 归档后它们是死条目,无害 —— 授权项只是「允许跑」,不会自己跑。要清就本机手动清。
D. 归档前另核实两条事实(正文没要求,但值得落纸):
- 本仓两个 remote 都不指老仓:
github→ttfishnet/lampio.git,origin→gitee.com/ttfishnet/lampio.git。 - 老仓本地工作树
~/reading_vocab_helper仍在,origin→ gitee 老仓,HEAD =7ffb05d, tagpre-rb-merge在场,master与origin/master同步。⇒ 回滚源完好,可以放心「只停推不删」。
E. 留给用户的操作(gitee 网页):仓库 → 管理 → 归档 / 设为只读。 🔒 不要删仓 —— 它是 pre-rb-merge tag 的宿主与最后的回滚源。 本机那份 ~/reading_vocab_helper 也建议留着。
T4-10 修 ci-rvh.yml 恒红 🔴
不是 T4 原有项,2026-08-29 首次去看 CI 时发现后补进台账 (此前 §7 只记着「clone 下来的 rvh/ 不能直接构建」,没人把它和「那 CI 岂不是永远跑不起来」连起来)。
事实:ci-rvh.yml 自 T3-6 建立起 4 次运行全 failure(e81cb2f6 / 6f26239a / 9286fe69 / f82b261e),一次都没绿过。T3-9 离场检查报的「flutter analyze 0 error / flutter test 1359 passed」是本机结果,没查 CI。
两个独立死因(都不是合仓引入的,但合仓把它们从「没人跑」变成「每次 push 都红」):
| # | job | 死因 |
|---|---|---|
| ① | Code Analysis | flutter pub get exit 66 —— pubspec.yaml 的 dependency_overrides 指向 local_packages/opencv_dart(100MB,被 rvh/.gitignore 忽略,git 里没有)。本机能跑是因为那份包只存在于本机 |
| ② | Architecture Compliance | lib/features/word_lookup/presentation/providers/word_lookup_providers.dart:7 直接 import 了 vocabulary_filtering/data/models/vocabulary_item_model.dart,命中 Presentation→Data 违规 grep。真实违规,早于合仓 |
① 需要先定方向再动手(三条路,各有代价,别自行决定): vendoring 那 100MB 进仓 / 换掉 opencv_dart 依赖 / 让 analyze job 不依赖它(如 CI 侧 dependency_overrides 剥离或 stub)。第三条最轻但会让 CI 与本机验的不是同一份依赖图。
② 是真违规,要么修 import(走 domain 层),要么裁定那条 grep 规则该放宽 —— 二选一,别只关闸门。
🔒 在修好之前,别把 ci-rvh.yml 当成任何东西的兜底(根 CLAUDE.md §9 已写死这条警告)。 本仓记着「一道恒红的闸门等于没有闸门」的实测教训(2026-08-18 起 CI 连红 6 天,纯文档 commit 也挂)。
✅ T4-10 结果(2026-08-29,CI 首绿 = run 33225931007)
0. 死因是 4 个,不是 2 个。 上面记的两个都属实,但它们互相遮挡: flutter pub get 一失败,analyze job 后面的步骤全不执行;架构 job 的检查 1 一 exit 1, 检查 2-5 从建立起一次都没跑过。修掉前两个之后,另外两个才露出来 —— 这正是「恒红闸门」的复利代价:你不知道它后面还堵着什么。
| # | job | 死因 | 处置 |
|---|---|---|---|
| ① | Code Analysis | flutter pub get exit 66(dependency_overrides 指向 gitignored 的 local_packages/opencv_dart) | 路 C2:override 移进本机专属 pubspec_overrides.yaml |
| ② | Architecture | word_lookup_providers.dart import data 层 model | 修代码:走 domain 层 |
| ③ | Code Analysis | .env 是 pubspec 声明的 asset 但不进 git ⇒ asset_does_not_exist(warning,--no-fatal-infos 挡不住) | CI 加 cp .env.example .env |
| ④ | Architecture | 检查 2/3/5 首次执行,抓到 8 处 | 7 处 DI 装配 → 收窄闸门;1 处真违规 → 修代码 |
| ⑤ | Code Analysis | FLUTTER_VERSION=3.24.0 解析不了本项目依赖(intl: ^0.20.2 vs flutter_localizations 钉的 0.19.0) | 对齐 3.38.5 + 删 dart format 门(用户裁定,改判 T3-6) |
| ⑥ | Unit Tests | sqlite3 native asset hook 从 third_party/sqlite3/ 读预编译库,只 vendor 了 arm64-android + x64-macos,ubuntu runner 要 x64-linux ⇒ Building native assets failed,整个 job 零测试执行 | vendor 第三个目标 libsqlite3.x64.linux.so(1.8MB,sha256 与包内 asset_hashes.dart 逐字相符) |
1. ① 的方向由用户拍板走「路 C」,落地取 C2。 三条路的取舍先做了实测再上会: 本地那份 opencv_dart 与 pub.dev 的 1.4.5 逐文件比对只差 2 个 native 构建文件 (android/build.gradle 的 abiFilters;src/CMakeLists.txt 关掉 OpenCV 下载 + 把 dartcv 源指向 本机绝对路径 /Users/larry/Downloads/dartcv-main),pubspec.yaml 与 lib/** 字节相同, Dart API 全部来自共同的 dartcv4: 1.1.8。⇒
- 路 A(vendoring)按字面做不成立:包里写死了本机绝对路径,且那 100MB 里 87M 是
android/.cxx构建缓存、13M 是jniLibs/*.so构建产物,真正的包本体不到 200KB; 而 CI 四个 job 没有一个构建 native,收益为 0。 - 路 B(换依赖):11 个 Dart 文件在用(整条 OCR 前处理链),与「修一道闸门」不成比例。
- 路 C 的等价性因此是可量化的,不是赌:CI 看到的依赖图与本机一致。 落地为 C2(移进
pubspec_overrides.yaml)而不是 C1(CI 侧 sed 剥离),额外收益是 干净 clone 从此能flutter pub get——rvh/CLAUDE.md与QUICKSTART.md开头那条 「clone 下来直接失败」的警告随之作废,两处已改写。 ⚠️ 代价(已写进三处文档):本机装了pubspec_overrides.yaml后flutter pub get会把pubspec.lock里 opencv_dart 那条从 hosted 改回 path,仓里提交的是 hosted 版,本机那次改写别提交回去。
2. ② 与 ④ 里那 1 处真违规,都按「走 domain 层」修,没关闸门。
word_lookup_providers.dart:它 import data model 不是 unused import ——m.toEntity()是定义在 model 文件里的 extension,删 import 会undefined_method(注入实证过)。给VocabularyRepository加searchVocabularyByPrefix(返回 entity)+ impl 转调同一个 datasource 方法,provider 改用已有的vocabularyRepositoryProvider。vocabulary_notebook/domain/services/review_source_loading_service.dart(检查 5,domain 真的依赖 data): 改注入 domain 的ReadingPageRepository。行为等价 —— 该 repository 的getSourcesForWord就是转调同一个 datasource 方法再toEntity(),不额外过滤(含 userId)。 顺带把 4 处dynamic source改成ReadingPageEntity source:这是本次唯一有运行期风险的改动, 打上真类型后字段对不上从运行期崩溃变成编译期报错(9 个字段名两侧一致,已核); 随之删掉 12 处as String?—— 类型确定后source.sourceType as String? ?? 'text'会触发dead_null_aware_expression(warning,会让 analyze 红)。
3. ④ 里那 7 处 DI 装配:收窄闸门,不改代码。 (🔴 归属说明,别记错:①「走路 C」与 ⑤「对齐版本 + 删格式门」是用户拍板; ④ 里那 1 处 domain→data 真违规「本轮就修」也是用户拍板。 本条是执行方的判断 —— 当时把「挪进 shared/」的风险与收益摆出来问, 用户回问的是「那种方式是不是最干净优雅、要不要现在做」,答案是「不是最优雅, 它是为迎合检查器而改结构」,据此收窄闸门并把一致性问题记进 backlog。 用户当时说了「如果仍想要集中装配再说」——那条路仍然开着。) 7 处全是 Riverpod provider 在装配具体实现。判据两条写进了 workflow:① 组合根必须知道具体实现类, Clean Architecture 禁的是业务/UI 逻辑依赖 data 类型;② 本仓 lib/shared/providers/vocabulary_filtering_providers.dart 在做同一件事,只因不在 features/*/presentation/ 下而 grep 够不到 —— 这条规则从写下之日起就与 本仓实践不一致,只是没人跑过所以没暴露。豁免只给 presentation/providers/, 且检查 1(data model 跨层)刻意不豁免(②修的那个正住在 providers/ 下,仍会被抓)。 「两套装配写法并存」作为一致性问题记进了 backlog.md。
4. 顺手修掉 §7 那条「flutter analyze 本机 exit=1」。json_serializable / riverpod_generator 同时列在 dependencies 与 dev_dependencies, unnecessary_dev_dependency 各报一条 warning。删掉 dev_dependencies 里的重复项, 两个包仍由 dependencies 提供,pubspec.lock 零变化。
5. 本机验收(在与 CI 同构的依赖图上跑的 —— 无本地 override): flutter analyze --no-fatal-infos exit 0(此前恒为 1)· flutter test 1359 passed(与 T2-1 / T3-9 基线逐字相同)· 5 条架构 grep 手工逐条跑过,全 clean。
5b. 第一次真实运行(run 33225053285)当场证伪了 T3-6 的一条裁定 —— 这就是死因 ⑤。 Architecture Compliance 首次变绿(5 条 grep 全过),Code Analysis 换了个死因:
Because reading_vocab_helper depends on flutter_localizations from sdk which depends on
intl 0.19.0, intl 0.19.0 is required.
So, because reading_vocab_helper depends on intl ^0.20.2, version solving failed.T3-6 钉 3.24.0 的理由是「让 dart format --set-exit-if-changed 在它被写就的版本下继续有意义」。 实测 flutter_localizations 各版本的 intl 钉法:3.24 / 3.27 / 3.29 都是 0.19.0, 0.20.2 最早出现在 3.32.0(Dart 3.8);而 tall-style formatter 从 Dart 3.7 起。 ⇒ 凡能解析本项目依赖的 Flutter 都带 tall style ⇒ 那道格式门在 3.24 上 从来没有、也不可能跑起来(pub get 先失败,谁也没走到过它)。 用户裁定:对齐 3.38.5 + 删掉全仓 format 门 —— 删的不是一道在工作的闸门,blame 零影响; 「要不要做一次全仓 tall-style 重排版」记进 backlog.md,别顺手做。 附带收益:CI 与开发机同版本,「本机验过的就是 CI 会验的」,§7 那条随之了结。
5c. 第二次运行(run 33225295455):Code Analysis 与 Architecture 双绿,Unit Tests 露出死因 ⑥。PathNotFoundException: .../rvh/third_party/sqlite3/libsqlite3.x64.linux.so + Building native assets failed ⇒ 整个 job 零测试执行。 处置照抄该目录 README 自己记着的前一次教训(2026-08-03:只 vendor 了 android .so, 缺 macOS .dylib 让本机整套测试跑不了、测试与代码脱节而无人察觉)—— 判据不是「有几个开发者」,是「有几个跑 flutter test 的平台」,CI 是其中之一。 补 libsqlite3.x64.linux.so(1.8MB;sha256 b177291845e… 与 sqlite3-3.5.1 包内 asset_hashes.dart 的期望值逐字相符)+ .gitignore 加 ! 例外 + README 的三处清单 (文件表 / 校验值表 / 升级流程的 for 循环)与 pubspec 注释同步改成三个目标。
5d. 第三次运行(run 33225652040):三个 job 里两个绿,Unit Tests 只剩 1 条失败 —— 而那 1 条是我自己造成的,值得记:1338 tests passed, 1 failed —— 失败的是 sm2_golden_test.dart, Cannot open file, path = 'test/fixtures/sm2-golden-vectors.json'。 🔴 根因是 CLAUDE.md §8 记着的「路径 C 暂存区级」陷阱当场复现:本会话在同一棵工作树上 并行推进 T4-3,git rm 那份副本时立刻进了暂存区,于是死因 ⑥ 那次 git commit(显式 git add 的是另外四条路径)仍然把整个 index 提交走了 —— 8feade41 因此带走了 T4-3 的文件删除,而配套的 Dart 路径改动还在工作树里没提交。 1338 + 21(该文件的断言数)= 1359 正好对上本机基线,说明它是整个文件加载失败、 不是某条断言不符。提交 T4-3(4cf65582)后自洽。 ⚠️ 本仓给这条陷阱开的药方就是「提交用 git commit <pathspec> 绕过 index」, 而我这次没用 —— T4-3 那次提交改用了 pathspec,才没重犯。
5e. 🎉 第四次运行(run 33225931007)四个 job 全绿。 Code Analysis ✅ · Architecture Compliance ✅ · Unit Tests ✅ (1359 tests passed, 5 skipped 与本机、与 T2-1/T3-9 基线逐字相同)· Coverage Report skipped(if: github.event_name == 'pull_request',push 上本就不跑)。 这是 ci-rvh.yml 自 2026-08-28 建立以来第一次绿(此前 5 次运行全 failure)。
6. 开工时列的「不能保证首次就绿的两点」,结果两条都没成为问题,但另外两条成了 (记下来是因为预判的风险不是实际的风险,下次别照着老清单放心):(都属 §7「CI 与开发机差一个大版本」那条的延伸):
—— 已随死因 ⑤ 一并解决:版本对齐后本机与 CI 同一个 formatter,且那道门已删。dart format在 Dart 3.5(CI)与本机 3.10 判定不同,本机无法预验pubspec.lock里 236 个包的url是https://pub.flutter-io.cn(本机镜像), GitHub runner 能否访问未经证实 —— 实测没问题,四次运行的 pub get 都从该镜像正常下载。
而实际拦路的是没预判到的两条:⑤ 钉死的 Flutter 版本根本解析不了依赖(5b)、 ⑥ CI 的 ubuntu 是第三个跑 flutter test 的平台(5c);外加一条自己造的(5d)。
7. 已知的悬而未决
| 项 | 状态 | 影响 |
|---|---|---|
✅ 已实测(2026-08-29,T4-5):能。flutter build bundle --target-platform=android-arm64 产出的 build/flutter_assets/assets/databases/lampio_dict.db 是真实的 65,777,664 B db,sha 与源一致,AssetManifest.bin 里有它;flutter test 1359 passed / flutter analyze --no-fatal-infos exit 0 均与基线相同 | 退路(build 期 copy)未采用且不建议——它的卖点「git 里只有一份」被 git 自身的内容寻址证伪,却要新增一个隐式构建前置。iOS 未单跑 Xcode,但资产拷贝走的是同一个 BundleBuilder | |
| Gitee 体积配额 | 待用户查(P0-4) | 影响 T3-2 是否加 --path 清 blob |
| 子目录 CLAUDE.md 是否真按需加载 | ✅ 已实测(P0-2,判定 A) | 地基成立。遗留:全程用 Bash 读文件的会话拿不到 rvh/CLAUDE.md,见 §2 P0-2 结果 |
RVH assets/sql/01_create_tables.sql 是第三份 schema 定义 | 已知未处理 | RB migrations.rs / Supabase sync-tables.sql / RVH 这份,三处描述同一批表。合仓后可考虑机械对齐,但不在本计划范围 |
FLUTTER_VERSION=3.24.0 vs 本机 3.38.5 | ✅ 已了结(2026-08-29,T4-10 改判):对齐 3.38.5 —— T3-6 的裁定前提被证伪(3.24 连 pub get 都过不去:intl ^0.20.2 最早由 Flutter 3.32 满足,而 tall style 从 Dart 3.7 起 ⇒ 那道格式门不可能在 3.24 上跑起来)。格式门已删,「要不要全仓重排版」记进 backlog。以下为原裁定记录: | 对齐会让 dart format 门当场红(实测 591 文件改 502,Dart 3.7 "tall style" 重写),或逼一次冲掉全部 blame 的重排版。代价:CI 验的版本与开发机差一个大版本,本机能过的未必 CI 能过、反之亦然。真要对齐得单开一轮:先提重排版、再升版本,且那一轮别和别的改动混在一起 |
check-doc-links.mjs 不在 CI 里 | ✅ 已处理(2026-08-29,T4-2 搭车) | 悬挂数清到 0(7 处补 archive/、3 处占位符由 PLACEHOLDER_RE 排除)后接进 ci.yml + package.json 的 check:doc-links |
🔴 rvh/CLAUDE.md 里的 grep 守卫没有任何东西在执行 | 2026-08-28 实测确认 | 那些块合仓前标着「自动化校验(CI grep)」,但没有任何 CI job / 脚本 / skill 引用它们(ci-rvh.yml 只跑 analyze + 5 条架构 grep + test)。它们是判据规格不是闸门;真正在守的是每条点名的 Dart 回归测试。T3-8 已把标签改成「守卫判据」并在节首写明。接进 ci-rvh.yml 是笔独立的活,接上之前别把「grep 写着」当「已经守住」 |
ci-rvh.yml 自建立起一次都没绿过rvh/ 不能直接构建 | ✅ 已了结(2026-08-29,T4-10):首绿 = run 33225931007,四 job 全 success,flutter test 1359 passed 与本机基线逐字相同。共 6 个死因(前 4 个互相遮挡,后 2 个推上 CI 才看得见),逐条见 §6「T4-10 结果」。干净 clone 现在能 flutter pub get;仍需本机 opencv_dart 的只剩「构建 Android APK」。下面保留当时的诊断记录:2026-08-29 首次查 CI 才发现(4 次运行全 failure:e81cb2f6 / 6f26239a / 9286fe69 / f82b261e) | 两个独立死因,都不是本次合仓引入的,但合仓把它们从「没人跑」变成「每次 push 都红」:① Code Analysis:flutter pub get 直接 exit 66 —— pubspec.yaml 的 dependency_overrides 指向 local_packages/opencv_dart(100MB,被 rvh/.gitignore 忽略,git 里没有)。本机能跑是因为那份包只存在于本机。② Architecture Compliance:lib/features/word_lookup/presentation/providers/word_lookup_providers.dart:7 直接 import 了 vocabulary_filtering/data/models/vocabulary_item_model.dart,命中 Presentation→Data 违规 grep(真实违规,早于合仓)。⇒ ci-rvh.yml 现在就是一道恒红闸门(本仓已记过「一道恒红的闸门等于没有闸门」的教训,2026-08-18 起 CI 连红 6 天)。T3-9 离场检查报的「flutter analyze 0 error / flutter test 1359 passed」是本机结果,没查 CI。修它是独立一笔活(要先定 opencv_dart 怎么进 CI:vendoring / 换 dep / 让 analyze 不依赖它),在修好之前别把 ci-rvh.yml 当成任何东西的兜底 |
flutter analyze 在本机 3.38.5 下 exit=1 | ✅ 已处理(2026-08-29,T4-10 搭车) | 那 2 条 warning 是 json_serializable / riverpod_generator 同时列在 dependencies 与 dev_dependencies(unnecessary_dev_dependency)。删掉 dev_dependencies 里的重复项即清零,两个包仍由 dependencies 提供,pubspec.lock 零变化。现 flutter analyze --no-fatal-infos exit 0 |
根 ci.yml 没有 paths 过滤 | 既有行为,本次未动 | ⇒ 只改 rvh/(或 admin//landing/)的 push 照样触发桌面端 CI。只烧额度不产生错误结论,且改的是桌面端主闸门、风险高于收益,故留着 |
8. 如果 P0-2 判定"恒加载"(方案作废时的退路)
那么拆分不省 token,合并会让 RVH 会话固定开销从 ~28k 涨到 ~49k。此时:
- 仍然值得做的:
docs/cross-end/归并(T4-1)、sm2-golden-vectors.json去重(T4-3)、 预装库去重(T4-5)——这三项收益与 CLAUDE.md 加载机制无关。 可以在不合并的前提下,靠"把共享资产集中到 RB、RVH 侧改为构建期拉取"部分实现。 - 不值得做的:整仓合并。收益被每会话 +21k tokens 吃掉。
- 另一条路:把根
CLAUDE.md压到极限(比如 3k,只留地图 + 指针), 让"恒加载"的绝对成本降到可接受。此时合并重新变得划算,但阶段 1 的工作量显著上升。
无论走哪条,结论要写回 rvh-merge-evaluation.md 的状态行, 别让那份评估继续挂着"倾向合并"误导后来的会话。