## Anki 牌组迁移护栏示例 ### Before 一次歌词牌组升级会在生成 `.apkg` 后直接走人工判断: 1. 先导出备份。 2. 看 `deck:"歌词"` 是否等于旧 tag 的 43 张。 3. 删除旧 deck。 4. 导入新 `.apkg`。 5. 再查 deck 和 tag 数量。 这个流程能工作,但每个项目都要重新写一次判断。遇到用户中途手动加卡、note type 被多个 deck 共用、AnkiConnect 不支持新增模板时,风险会靠临场经验兜底。 ### After 把迁移动作变成统一 guard: ```bash python learning-bu/tools/anki_migration_guard.py \ --deck '歌词' \ --mode replace-deck \ --apkg english/04-projects/lyrics-cloze-2026-05-09/lyrics-sequence-v2.apkg \ --old-tag lyrics_cloze_20260509 \ --new-tag lyrics_sequence_v2_20260509 \ --expected-old-count 43 \ --expected-new-count 81 \ --backup-dir english/04-projects/lyrics-cloze-2026-05-09/backups \ --dry-run ``` Dry-run 输出: | 检查项 | 成功信号 | 失败信号 | | --- | --- | --- | | AnkiConnect 可用 | version 返回正常 | Anki 未打开或插件不可用 | | 旧 deck 范围 | `deck:"歌词" = 43` 且旧 tag = 43 | deck 数大于旧 tag,说明可能有手工新增卡 | | 新包预期 | `.apkg` 可解析且新 tag = 81 | 包内 tag/card 数与预期不一致 | | 备份 | backup apkg 已写入 | 备份失败则禁止迁移 | | 执行路径 | replace-deck 可用 | note type/template 变更则切到 collection API | 确认安全后执行: ```bash python learning-bu/tools/anki_migration_guard.py ... --execute ``` 执行后必须给出验证摘要: ```text backup: backups/歌词_before_2026-05-09_1146.apkg removed_old_cards: 43 imported_new_cards: 81 verify deck:"歌词": 81 verify tag:lyrics_sequence_v2_20260509: 81 manual_cards_detected: 0 status: success ``` ### 钢琴模板拆分场景 对于 `钢琴 基础` 这种不是替换 deck、而是给既有 note type 增加 card template 的迁移,guard 的模式应换成: ```bash python learning-bu/tools/anki_migration_guard.py \ --deck '钢琴 基础' \ --mode add-template \ --note-type 'Piano Staff Card' \ --template-name '这张谱用哪个手指?' \ --expected-before-count 20 \ --expected-after-count 30 \ --preserve-review-template '这张谱读什么?' \ --execute ``` 这里的核心不是删旧卡,而是确认原复习记录留在旧模板上,新增卡只来自新模板。若 AnkiConnect 只更新已有模板而没有新增模板,guard 应明确失败并提示切换 Anki collection API。