Skip to content

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,tag pre-rb-merge = 7ffb05d), 并注意 T3-2 已被 T2-3 裁定加了两条 --path(清 USER_NOTES.md 与那份 PDF 的历史)。阶段 1 独立成立, 不继续合并也已受益:根 CLAUDE.md 73015 → 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-check S28 基线守恒)。合并落地后需要收敛的红线, 按 CLAUDE.md §4「红线归属规则」写进稳定归属,不留在本文件。


0. 冷启动开工指引(新会话先读这段)

你如果是一个没有上下文的新会话,按顺序做三件事:

  1. 读 §1 台账,找到第一个不是 ✅ 的任务。
  2. 读该任务所在阶段的阶段前言(讲清这一阶段的不变量与离场条件)。
  3. 读该任务本身。每个任务都写了「在哪个会话做 / 前置 / 动作 / 验收 / 回滚」, 不需要回看创建它的那次对话。

三条硬约束,任何会话都不许违反:

  • 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 + gitleaksRB2026-08-28 · filter-repo 2.47.0 / gitleaks 8.30.1
P0-2实测子目录 CLAUDE.md 按需加载新开一个2026-08-28 · 判定 A 按需加载(限定:仅文件工具触发注入,Bash 不触发)
P0-3RVH history 完整 secret scanRB2026-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写归属裁定表(每节 → 去处),落进 §3RB2026-08-28 · 见 §3 T1-1 裁定结果 A/B/C/D;改判 3 处(Auth&Sync / 2-Button / Supabase 三小节留根)+ 改判 T1-4 验收数字
T1-2src-tauri/CLAUDE.mdRB2026-08-28 · 21.4 KB / 5 节 / 承接 15 条红线(#1-#7 · #11)
T1-3src/CLAUDE.mdRB2026-08-28 · 7.0 KB / 4 节(建了,未 ⏭:导航模型 + 6 行约定索引够本)
T1-4CLAUDE.md 瘦身 + 加路由段RB2026-08-28 · 73015→50542 B(-30.8%)· 顶部加五行路由表 + Bash 不触发注入的告警
T1-5守门接线(check:claude-paths 传参 + ci.yml)RB2026-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 未推)RVH2026-08-28 · 基线已过期:这批工作已由 361dd6d / 6555c1e 等提交先行收口,验收三条全绿(status 干净 / ahead 0 / flutter test 1359 passed)。🔑 黄金向量只改了 _doc_consumers 两个说明字段,expected 一个没动 → RB 两个消费方不需要同步
T2-2删僵尸分支 + 清 worktreeRVH2026-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.mdRVH2026-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-pathsQUICKSTART.md 保留原地不迁 docs/。四处引用已同步改掉
T2-4删死历史(supabase/migrations/ · tools/vocabulary_builder/RVH2026-08-28 · 7ffb05d · migrations 整目录删除;vocabulary_builder 改 .gitignore 忽略(磁盘上 828M 本地产物按用户裁定保留不动)。10 处活跃文档引用同步改掉。验收:flutter test 1359 passed(与删前逐字相同)·flutter analyze 唯一 error 落在已退出版本控制的 tools/vocabulary_builder/,删前就存在
T2-5打 pre-merge tag 作回滚锚 + 推 originRVH2026-08-28 · tag pre-rb-merge7ffb05d,已推 gitee(git ls-remote --tags 复核过)。离场条件全部满足:status 0 行 · ahead/behind 0 0 · 只剩 master · 无额外 worktree

阶段 3 · 合入(RB 会话;原子区,中途不可停

ID任务会话状态日期 / commit
T3-1双仓备份(tag + 本地镜像 clone)RB2026-08-28 · tag pre-rvh-mergec10a8f9,github + gitee 双推;镜像 /tmp/rvh-mirror.git(435M,与源仓 .git 硬链等大)
T3-2在临时克隆里 filter-repo 造前缀化历史RB2026-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-3merge --allow-unrelated-histories 合入RB2026-08-28 · 509da57 · 工作树干净、blame 穿透保持 6。顺手删掉 fetch 带进来的 pre-rb-merge tag(它指向改写后的 SHA,与 RVH 仓里那个同名 tag 不是一个对象,留着会误导跨仓比对;权威锚点仍在 RVH 仓)
T3-4.gitignore / .gitattributes / .mcp.json 就位RB2026-08-28 · 抓到一处真冲突:根 .gitignoredata/ 无锚定 ⇒ 连带匹配 rvh/data/(133 个已跟踪文件)。已跟踪的不受影响 ⇒ git status 一直干净 ⇒ 只有新增文件被静默忽略,最难发现的那类。修法是加 !rvh/data/ 反选(不能直接锚成 /data/ —— RB 侧 tools/vocabulary_builder_v3/data/ 的 GB 级文件正靠它挡)。另:.gitattributesrvh/assets/databases/*.db binary.mcp.jsonrvh-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-5Skill 作用域(RB 四个排除 rvh/;RVH 八个变 scoped)RB2026-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.ymlRB2026-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-7check-claude-md-paths.mjs 的 ROOTS 加 rvhRB2026-08-28 · 两处都改了:ROOTS 白名单 + package.jsoncheck:claude-paths 文件清单,并在脚本里留注释说明「新增子项目要动两处」。两条验收都跑了:注入 rvh/lib/nonexistent.dart 后失效路径 7→8(证明脚本真的在校验 rvh/),grep 确认 CI 那条命令里出现了 rvh/CLAUDE.md
T3-8rvh/CLAUDE.md 去镜像 + 根 CLAUDE.md 加 rvh 路由RB2026-08-28 · 见下方「T3-8 结果」。105415 → 78365 B(-25.7%),未达正文的 <40000 B —— 原因与证据见 §7
T3-9离场检查:两端都能编译 + 全部守门绿RB2026-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-1docs/cross-end/ 两半归并 + 编号消歧(不复原)任意2026-08-29 · 32 文件归并 + 2 份去重;🔴 「编号复原」改判为「不重编号 + README 消歧」(理由见下方 T4-1 正文「实际做法」);悬挂 19→10
T4-2cross-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-3sm2-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-565MB 预装库去重(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-6sync-rvh-vocabulary.sh · 简化 verify-rvh-alignment.sh任意2026-08-29 · 0915de31 · 见下方「T4-6 结果」。🔴 两项都改判:① sync-rvh-vocabulary.sh 不删、改型 —— git mvscripts/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 analyzeasset_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.ymlFLUTTER_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.tsadmin/CLAUDE.md 全文注入,两个 admin 探针立刻答得出。 附带现象:admin 的三个 scoped skill(deploy / admin:code-review / admin:doc-sync-check) 也是同一刻才注册进工具集——skill 与 CLAUDE.md 同批按需加载,不是只有文档。

⚠️ 限定条件(必须带进阶段 1 的设计里):注入挂在文件工具上,Bash 不触发。

(原文这里用的是红线锁标记,已改为 ⚠️ —— 本文件 header 承诺不含锁标记, 那会让 /arch-check S28 的 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.stagingassets/.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 用户确认后裁定)

两项事实把严重性从「事故」降到「卫生问题」:

  1. gitee ttfishnet/reading_vocab_helper 是私有仓 → 从未对外暴露。
  2. 百度 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.ymladmin/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 架构要点1592src-tauri/CLAUDE.md(multi-webview / IPC / DB 双通道 / 三条设计原则)
§4 技术红线5132:#5i / #6d / #9 / #10 留根(双端契约);#1-#8 / #11 下沉
§5 数据库1758src-tauri/CLAUDE.md
§6 安全规则215
§7 项目 Skills1696根(但可压缩: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 非显然约定索引4479snapshot.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 顶层导航模型1444src/React Workspace 模块,纯前端
§2 Rust Commands 模块1536src-tauri/原则 2
§3 Multi-Webview 架构1082src-tauri/webview 是 Tauri 概念,建窗在 lib.rs
§3 DB 双通道184src-tauri/原则 2
§3 Content-Script IPC167src-tauri/content-script 源在 src-tauri/src/content-script/
§3 Auth & Sync2640 ⚠️ 改判(§3 建议列原写 src-tauri/同步矩阵 / 墓碑传播覆盖 / merge 策略 / cloze 两条 🔒 全是 RB⇄RVH 共写语义(cross-end/23 明写放开 RVH 采集路径)。下沉 = 改 Dart 的会话永远看不见 → 原则 1
§3 2-Button 复习系统335 ⚠️ 改判正文自称「三端行为契约」→ 原则 1
§3 架构设计原则(3 条)1123src-tauri/src/ 留一行指针讲 Tab/webview/跨边界通信,落点在 Tauri 边界
§4 红线归属规则1828(压缩 admin force-dynamic 那段举例为一句)它是治理元规则,且本次拆分后多了两个归属去处,必须留根
§4 红线表 20 条15459,见下表 B
§5 迁移版本3399src-tauri/RB 本地迁移链,RVH 有自己的一套
§5 预装数据2145src-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-equal2427「RB … 与 RVH … 字节相等」
#1 rusqlite 锁 v0.3178src-tauri/
#2 禁 LOWER()COLLATE NOCASE80src-tauri/
#3 content-script 用 __TAURI_INTERNALS__107src-tauri/
#4 DOM 操作前断开 MutationObserver101src-tauri/
#5 pull 用 server_updated_at=gt.135src-tauri/
#5a last_sync_aterrors.is_empty() 推进174src-tauri/
#5b pull_* INSERT 必须写 user_id254src-tauri/
#5c 登出必须 resetAllTabs()235src-tauri/
#5d watermark = 实际返回行的 MAX710src-tauri/
#6 软删用 deleted_at103src-tauri/
#6a 父行删除显式软删子树2022src-tauri/
#6b push payload deleted_at 传真值327src-tauri/
#6c pull 父行守卫三态1430src-tauri/
#7 save_word 复活软删条目76src-tauri/
#11 已出厂迁移一个字节不能改1642src-tauri/
#8 组件内禁 hex 字面量66src/
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 315Auth&Sync + 2-Button
§12 文件组织3 119
§7 Skills2 3044 303 → 压缩后
§10 / §5 / 头部 / §1 / §6 / §目录 / §118 052

实施中对 A 表的两处偏离(先记账,不藏):

  1. §2「目录布局」与「仓库地图」没有合并。 A 表写的是「合并(两处画同一棵树,是第二本账)」。 动手时判断这个框定不成立:前者标注每个目录是干什么的(含 pages/content-script.js 两个 ⚠️ 陷阱),后者标注本体 vs 卫星与耦合方向。合并必然牺牲其中一个框定, 而仓库地图那段自己就写着「这解释了目录树的'不对称'——它是结构事实,非命名问题」。 代价 ~1.8 KB,值。
  2. §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 实际验收(三条,全部已通过)

  1. node scripts/check-claude-md-paths.mjs CLAUDE.md src-tauri/CLAUDE.md src/CLAUDE.md 退出码 0 ✅
  2. B 表 16 条下沉红线在根 §4 全量索引里各有一行(索引 20 行 = 4 条留根 + 16 条下沉)✅ —— 这是防「为凑数字删约束」的真闸门
  3. 行级无丢失核对git show HEAD:CLAUDE.md 的每个实质行(≥25 字符)都能在新三文件中找到, 例外仅限「裁定内的重写/压缩」与 ../ 路径改写造成的假阳性,逐条过目 ✅ (据此补回了三处真丢失:非显然索引前言的考古依据 ×2、/vocab-reseed 的「收到 RVH 交接文档」触发、 动作类 skill 的深度标注)
  4. 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]}")
EOF

T1-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.md

T1-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(它接受额外文件作参数)。 新建的子文件不接线 = 它们的失效路径无人守。

改两处:

  1. package.jsoncheck:claude-paths 加上新文件参数
  2. .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.mdsrc-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 §4revisit.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.mjs19 处悬挂,与拆分前逐条相同,新文件零新增
行级无丢失核对(对 git show HEAD:CLAUDE.md✅ 见 T1-1 §D 验收第 3 条
红线索引 20 行 = 4 留根 + 16 下沉
package.json / ci.yml 语法

T1-6 只剩「新会话实测注入行为」这一件事。

结果(2026-08-28 新会话实测)

判定 = A:交接 prompt 定义的三个场景全部符合期望,按需叠加确实按预期工作。

步骤动作上下文里的 CLAUDE.mdQ1(七个)Q2(revisit 判据)Q3(跨项目决策参考)
1不碰任何文件全局 + 根 = 2 份⚠️ 见下答不出答不出
2Read src-tauri/src/lib.rs+src-tauri/ = 3 份✅ 答得出答不出答不出
3Read src/lib/moduleNav.ts+src/ = 4 份✅ 答得出答不出
4不碰 admin/仍 4 份✅ 仍答不出

两条如实备注,都不改变判定,但会影响下次复用这个 prompt 的人:

  1. 🔴 Q1 探针不干净,第 1 步就能答出「七个」。 唯一性核验(上面那张表)只 grep 了字符串 「七个模块已目录化」,而根 CLAUDE.md §2「已下沉的三小节」表里有一行写着 「Rust Commands 模块 → src-tauri/CLAUDE.md §4 → 七个已目录化模块的路径陷阱」—— 数字本身泄漏在恒加载的根文件里,只是措辞不同。本次靠「§4 正文的完整模块清单 (sync/pull 三层嵌套等)第 1 步确实给不出」区分开了真假绿,但这是运气不是设计。 下次换探针:问「sync/pull 拆成哪三块」或「reading 为什么是 5 块不是 3 块」, 根侧索引行结构上不可能含这类正文细节。教训与 P0-2 同源——探针唯一性要按语义核,不是按字符串核
  2. 计划原表的三场景里,只有第 1 个被完整验证。 prompt 实际跑的是 「src-tauri 正向 + src 正向 + admin 反向」;原表的「只读 admin/lib/queries.tsadmin/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 达成:tag pre-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

🔑 黄金向量的警报解除361dd6dtest/fixtures/sm2-golden-vectors.json 的改动是 2 行,只动了 _doc 说明和 _consumers.rvh_dart 指向(从"待新会话"改成实际测试文件路径), 没有任何 expected 值变动。故 srs.rs::sm2_golden_testssrc/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.md95KBdocs/git rm --cached 转本地
USER_NOTES.md完整流程使用说明.pdf157KB同上(另:文件名含中文 + 空格,违反 RB 的 kebab-case 约定)
QUICKSTART.md6.5KB保留,迁 docs/
个人记录.rtf26KB未 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:43undefined_function。 该目录只是退出版本控制、文件仍在磁盘上(用户裁定保留 828M 本地产物), analyze 照旧扫它。删除前它就在报同一条。合仓后 CI 跑的是新克隆、不含该目录 ⇒ 自动消失。 其余 3305 条全是 infotools/ 之外零 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

  1. .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 大文件根本不靠它。别照这一步的写法处理任何新的忽略规则。

  2. .gitattributes:根那份写着 * text=auto eol=lf(Windows CI 的 CRLF 坑)。 Dart 文件适用,无需改。但要给 rvh/assets/databases/*.dbbinary(比照现有那行 src-tauri/assets/lampio_dict.db binary)。

  3. .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-checkStep 2 的变更分流 grep -E '^(admin|landing)/'rvh;Step 5 加 rvh 分支(flutter analyze + flutter test
/ui-check同 arch-check,核对路径限定
/doc-sync-check加 rvh/ 文档映射

RVH 侧八个 skill 随目录进来成为 scopedrvh: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.pathsworkflow 级不是 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';

不加 rvhrvh/CLAUDE.md 里的仓内路径引用一条都不会被校验。

🔴 要改的是两处,不是一处(2026-08-28 阶段 1 之后新增的耦合): T1-5 把 package.jsoncheck: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.json

T3-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 禁止事项第三条)。

正确做法:

  1. 契约文字("为什么"、判据、反例、事故记录)→ 并进根 CLAUDE.md §4 对应条目。 同一条红线从此只有一处叙述。
  2. Dart 侧的落地形状与 grep 守卫("RVH 这边具体长什么样")→ 留 rvh/CLAUDE.md, 但改写成指向根条目 + 只讲本端差异
  3. 结果目标: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 / 强推这条路已经关死

建议顺序(不是硬依赖,只是省返工):

顺序为什么排这里
1T4-1最便宜 —— 实际不便宜:9 处自动变绿的同时会新增 12 处(移入正文里的 rvh-相对路径被搬迁打断),naive git mv 会让闸门 19 → 22。且「编号复原」改判为「不重编号 + README 消歧」。详见 T4-1 正文「实际做法」
2T4-2 + T4-3 一起T4-2 单做T4-2 要把 §D 的 heuristic 换成真读 sm2-golden-vectors.json,那正是 T4-3 的内容 —— 分开做必返工 🔴 2026-08-29 实测推翻:§D 早已改造完(脚本第 216 行已在读 JSON),两项耦合消失,T4-3 可独立排期。但 T4-2 本身变贵了 —— 脚本当前跑到 §B 就崩,接 CI 前得先修两个 bug。详见 T4-2 正文「开工前实测」
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 → 1git log -- <整个目录> = 1--follow 对目录无效, 只有单文件加 --follow 才是 7)—— 正是 T3-2 用来否决 git subtree 的那条判据。 git blame 仍能穿透。且仓内 281 个文件、934 个路径名带 rvhci-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-*.mdrvh-相对与仓根相对在这里恰好重合,所以 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 仍未做(脚本至今不在 .github/workflows/ 也不在 package.json),留给 T4-2。2026-08-29 T4-2 收尾时做掉:剩余 10 处中 7 处补 archive/ 前缀、3 处占位符 (24-...-handoff.mdNN-*-confirmation.md)改由脚本的 PLACEHOLDER_RE 排除, 降到 0 后加进 package.jsoncheck: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 见台账) —— 三点值得下一个会话知道:

  1. 按模式扫,抓到了第二处。只修报错那一行是 workaround;用 perl -ne 'while(/\$([A-Za-z_]\w*)([^\x00-\x7F])/g){...}' 全扫, 除第 129 行外还有第 233 行 $GOLDEN_JSON) —— 它在「黄金向量 JSON 缺失」的 else 分支里,平时走不到,是潜伏的同款。两处都改成 ${var},复扫已清零。
  2. 陷阱写进了脚本头set -uo pipefail 下方),含一行自检命令, 防止以后再写出「变量后直接接中文标点」。
  3. 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.dbCI 里根本没有这个文件 ⇒ 本机一套列源、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=Nskip() 表示「本次没验」、不计入退出码,于是路径写错 → 文件找不到 → 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:19File('test/fixtures/sm2-golden-vectors.json') —— cwd 相对flutter testci-rvh.ymldefaults.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.sh L1/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.dartFile('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「本次没验」⇒ 不会红,安静降级成不检查
L2grep -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 的 has6grep -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/lib33 处 LOWER(;#6 = clearLearningDataForUser 仍 hard DELETE 5 张表。

6. 没做的rvh/CLAUDE.md 的 11 个 grep 守卫块仍然没有任何东西在执行(§7 那条待决项)。 把它们接进 ci-rvh.yml 是独立的一笔活 —— 本项只动叙述归属,不动执行。

T4-5 65MB 预装库去重

src-tauri/assets/lampio_dict.dbrvh/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.ymlpathsrvh/**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 binaryrvh/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 test1359 passed / 5 skipped,与 T4-10 基线逐字相同
flutter analyze --no-fatal-infosexit 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.ymlruns-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 断言)+ 编号对照表那行 + 危险操作清单里「删预装库」那行
.gitattributesrvh/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 里装着两样删掉就没有替代品的东西,删它之前必须先给它们找到新家:

  1. 体积闸≥80 MB 告警 / ≥100 MB 判死并拒绝 cp。它挡的是 GitHub 单文件 100 MB 硬限 (超了 push 直接被拒)。当前资产 65,777,664 B,轨迹 8→52.7→62.7 MB, 按 /vocab-reseed skill 的估计只剩约 2–4 轮大扩容。删脚本 = 顺手关掉这道闸, 而它要挡的事故是「push 被拒、整轮 reseed 卡死」。
  2. 资产卫生断言: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.md 5 处(含上面 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 红线 #10RB 落地 段:闸门新家 = check-vocab-asset.sh,两道闸各是什么、谁跑它、CI 为什么不够;沿革加 T4-6 一行(含「读老文档遇到『跑 sync 脚本整包覆盖』= 今天的什么」换算)
.claude/skills/vocab-reseed/SKILL.md5 处 → 一句话定位改写 + Step 3 从「部署 + 反向 sync」改成「部署 + 过闸门」+ 明写「RVH 侧没有动作」;顺带订正那行写死的过期 lemma 行数(140370/101646 → 改为「以脚本为准」)
tools/README.md数据流图末端从 ~/reading_vocab_helper/...合仓后就没跟着改)改成 rvh/... + symlink
tools/vocabulary_builder_v3/README.md2 处(含一条可复制的命令)
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.dblampio_dict.db · notebook_entrieslearning_entries · 设备活库名也错着reading_vocab.dblampio.db,与预装 asset 是两个文件)· 版本锚散文写死 =25 而实际是 29 ⇒ 改成「只看代码」· 双仓绝对路径全部仓根相对
docs/plans/backlog.md3 处:LFS 那条的假绿论证(其中「两边都是 pointer 时报 byte-equal ✅」这条具体路径已随 §B 换型消失,但打包塞 pointer 那条仍然没人挡)· 体积闸「正确的家」那段补换家记录 · 短语 B 接力条里两个过期行数
docs/plans/proper-noun-governance-plan.mdrebake 豁免的收尾要求
.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_helper2026-08-28 已是本仓 rvh/恒加载文件里的错误地图
「RVH 待镜像表改名(table-rename-rvh-handoff.md)」RVH 2026-07-09 以 schema v56 镜像完成、07-10 三端 verified(docs/archive/09-*.mdrvh/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.md3可复制的部署命令 cd /Users/larry/reading_vocab_helper —— 照抄就在归档仓里 supabase link / functions deploy
supabase/functions/lookup-or-fetch-word/README.md1同上,一条可复制命令
tools/vocabulary_builder_v3/config.yaml 2 · 4 个 bin/*.dart · lib/quality_governor.dart6指向 ~/reading_vocab_helper/docs/plans/ 的三份计划 —— 三份都在本仓 rvh/docs/plans/,改成仓内相对后是活链接
admin/CLAUDE.md4🔴 控制文件说假话:仓库地图写「RVH ~/reading_vocab_helper」+「RVH 仍独立仓」+「RVH 在独立仓,需新会话镜像」。与 T4-7 在根 §9 查出的三处假话同型 —— 别只审自己那半边
docs/vocabulary-domain-knowledge.md1「这条流水线物理上住在 RVH 仓库」—— 过期两轮(2026-07-19 迁进 RB、2026-08-28 合仓),还顺带教人「改 pipeline 必须在 RVH 新会话」,而它是纯 dart run、RB 会话就能改
docs/plans/backlog.md7两份 RVH 开场 prompt 模板仍写「用 ~-锚定绝对路径读,禁止裸相对路径(CLAUDE.md §9)」—— 那条规则 T4-7 整节删了.mcp.json cross-reference 指引要人去编辑老仓那份(T3-4 已删),而根 .mcp.json 早就两个条目都有 ⇒ 整个步骤作废
rvh/docs/README.md · rvh/docs/guides/README.md3skill 目录指向老仓绝对路径(顺带订正「7 个」→ 实际 8 个)
docs/verification/cross-end-cloze-pool-2026-08.md3🔒 常驻验证清单(永不归档),却带着 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.jsonadmin/.claude/settings.local.json 里各有若干条指向老仓的 Bash 授权项(gitignored 的本机文件)。 归档后它们是死条目,无害 —— 授权项只是「允许跑」,不会自己跑。要清就本机手动清。

D. 归档前另核实两条事实(正文没要求,但值得落纸):

  • 本仓两个 remote 都不指老仓githubttfishnet/lampio.gitorigingitee.com/ttfishnet/lampio.git
  • 老仓本地工作树 ~/reading_vocab_helper 仍在origin → gitee 老仓,HEAD = 7ffb05d, tag pre-rb-merge 在场masterorigin/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 次运行全 failuree81cb2f6 / 6f26239a / 9286fe69 / f82b261e),一次都没绿过。T3-9 离场检查报的「flutter analyze 0 error / flutter test 1359 passed」是本机结果,没查 CI。

两个独立死因(都不是合仓引入的,但合仓把它们从「没人跑」变成「每次 push 都红」):

#job死因
Code Analysisflutter pub get exit 66 —— pubspec.yamldependency_overrides 指向 local_packages/opencv_dart(100MB,被 rvh/.gitignore 忽略,git 里没有)。本机能跑是因为那份包只存在于本机
Architecture Compliancelib/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 Analysisflutter pub get exit 66(dependency_overrides 指向 gitignored 的 local_packages/opencv_dart路 C2:override 移进本机专属 pubspec_overrides.yaml
Architectureword_lookup_providers.dart import data 层 model修代码:走 domain 层
Code Analysis.env 是 pubspec 声明的 asset 但不进 git ⇒ asset_does_not_existwarning--no-fatal-infos 挡不住)CI 加 cp .env.example .env
Architecture检查 2/3/5 首次执行,抓到 8 处7 处 DI 装配 → 收窄闸门;1 处真违规 → 修代码
Code AnalysisFLUTTER_VERSION=3.24.0 解析不了本项目依赖intl: ^0.20.2 vs flutter_localizations 钉的 0.19.0)对齐 3.38.5 + 删 dart format 门(用户裁定,改判 T3-6)
Unit Testssqlite3 native asset hook 从 third_party/sqlite3/ 读预编译库,只 vendor 了 arm64-android + x64-macos,ubuntu runner 要 x64-linuxBuilding 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.yamllib/** 字节相同, 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.mdQUICKSTART.md 开头那条 「clone 下来直接失败」的警告随之作废,两处已改写。 ⚠️ 代价(已写进三处文档):本机装了 pubspec_overrides.yamlflutter 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 (注入实证过)。给 VocabularyRepositorysearchVocabularyByPrefix(返回 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_expressionwarning,会让 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 同时列在 dependenciesdev_dependenciesunnecessary_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.dartCannot 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 与开发机差一个大版本」那条的延伸):

  • dart format 在 Dart 3.5(CI)与本机 3.10 判定不同,本机无法预验 —— 已随死因 ⑤ 一并解决:版本对齐后本机与 CI 同一个 formatter,且那道门已删。
  • pubspec.lock 里 236 个包的 urlhttps://pub.flutter-io.cn(本机镜像), GitHub runner 能否访问未经证实 —— 实测没问题,四次运行的 pub get 都从该镜像正常下载。

而实际拦路的是没预判到的两条:⑤ 钉死的 Flutter 版本根本解析不了依赖(5b)、 ⑥ CI 的 ubuntu 是第三个跑 flutter test 的平台(5c);外加一条自己造的(5d)。


7. 已知的悬而未决

状态影响
Flutter 能否用 symlink 资产已实测(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 这份,三处描述同一批表。合仓后可考虑机械对齐,但不在本计划范围
RVH CI 的 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.jsoncheck: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 自建立起一次都没绿过 + clone 下来的 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 Analysisflutter pub get 直接 exit 66 —— pubspec.yamldependency_overrides 指向 local_packages/opencv_dart(100MB,被 rvh/.gitignore 忽略,git 里没有)。本机能跑是因为那份包只存在于本机② Architecture Compliancelib/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 同时列在 dependenciesdev_dependenciesunnecessary_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 的状态行, 别让那份评估继续挂着"倾向合并"误导后来的会话。