用稳定笔记 ID 更新 Anki 牌组
为已经分发的 Anki 牌组发布修正或新增卡片。稳定笔记 ID 帮助 Anki Forge 和 Anki 识别已有笔记,而不是把它们当成新笔记;Anki 导入设置与较新的本地编辑仍会影响最终变化。
保持项目命名空间和笔记、模型、字段、模板键稳定。每个新版本都以上一版分发时的原始 APKG 为基准构建。包内包含完整的标识映射和修订信息,且这些信息采用带版本号的格式。请将原始包与对应的源码版本一起保存。
运行两个版本并比较
章节:运行两个版本并比较cargo run --locked -p ankiforge --example docs_workflow -- target/docs-examples这会写出 spanish-v1.apkg 和 spanish-v2.apkg,比较修改后的答案,并检查更新包的数量。可执行源码如下:
fn vocabulary(answer: &str) -> anyhow::Result<Project> { let mut project = Project::new("docs-spanish-updates")?.default_deck("Spanish"); project.add("hola", Note::basic("hola", answer))?; Ok(project)}
fn updates(output: &Path) -> anyhow::Result<()> { let previous = output.join("spanish-v1.apkg"); let next = output.join("spanish-v2.apkg"); vocabulary("hello")?.build(BuildOptions::to(&previous))?; let project = vocabulary("hello; hi")?; let comparison = project.compare(CompareOptions::against(&previous))?; ensure!(comparison.policy().allows_publication()); let built = project.build(BuildOptions::to(&next).update_from(&previous))?; verify(&built, 1, 1, 0)?; ensure!(built.report().comparison().is_some()); Ok(())}即使发现项会阻止发布,compare 也可以成功完成。请分别读取 comparison.policy().allows_publication()、原始风险级别和证据。build(...update_from(...)) 执行相同分析;若策略阻止候选包,则在发布前返回 BuildError。
策略与检查限制
章节:策略与检查限制这份完整程序选择更严格的阈值,并对比较和更新使用相同配置:
use ankiforge::{BuildOptions, Note, Project};use ankiforge::build::InspectLimits;use ankiforge::update::{CompareOptions, RiskLevel, UpdatePolicy};
fn main() -> anyhow::Result<()> { let mut first = Project::new("policy-example")?; first.add("cell", Note::basic("Cell?", "Unit of life"))?; first.build(BuildOptions::to("policy-v1.apkg"))?; let mut next = Project::new("policy-example")?; next.add("cell", Note::basic("Cell?", "The basic unit of life"))?; let mut limits = InspectLimits::default(); limits.max_collection_bytes = 1 << 30; let policy = UpdatePolicy::default().fail_on(RiskLevel::Medium); let comparison = next.compare(CompareOptions::against("policy-v1.apkg") .inspect_limits(limits.clone()).update_policy(policy.clone()))?; assert!(comparison.policy().allows_publication()); next.build(BuildOptions::to("policy-v2.apkg").update_from("policy-v1.apkg") .inspect_limits(limits).update_policy(policy))?; Ok(())}默认策略阻止 High 和 Critical 发现项。审阅特定类别后,调用方可有意选择 UpdatePolicy::default().allow(RiskCode::NoteRemoved)。它放行该类别中的所有发现项,同时保留原级别和证据;并不表示逐条批准指定笔记。没有匹配发现项的放行会产生警告;未知风险码被拒绝。不要自动放行比较返回的每一个码。
缺失或损坏的证据、命名空间不匹配、无效候选数据和检查限制都是硬错误,不是可放行的风险类别。分析无法完成时,比较返回错误及已完成的观察结果。对没有 update_from 的新建请求显式设置策略属于配置错误,与 setter 调用顺序无关。
证据与身份
章节:证据与身份包内包含 ankiforge-identity.json,格式为 ankiforge-identity-v1。它将模型或配置 ID、笔记 GUID、历史字段或模板槽位、卡片或遮罩序号、修订和媒体绑定到实际集合与载荷。Anki 重新导出不保留该证据,不能作为更新基线。缺失证据绝不会被静默重建。
显示名称和内容编辑保留身份键。内容未变时保留基线修订;内容改变时推进修订。字段或模板重排保留历史映射。退役身份继续保留,恢复相同键时可复用。笔记键不能移到另一模型,模型键也不能在普通与 Cloze 类型间改变。
客户端导入行为与发布检查
章节:客户端导入行为与发布检查APKG 是分发包,不是删除同步。省略笔记、字段、模板或遮罩,不会删除学习者已有数据。字段或模板 schema 和排序字段变化属于高风险,因为 Anki 合并与导入设置会影响行为。Schema 变化可能复制模型,或在笔记内容比较前更新目标笔记的时间戳。
在支持的客户端测试首次导入以及导入已有集合的更新。使用目标合并与笔记更新设置,检查字段、GUID、卡片集合、本地编辑和已有调度。仅稳定身份不保证较新本地编辑被覆盖,也不保证已删除的历史被恢复。
将候选包写到与归档基线不同的路径。审阅后分发被接受的包,并将它保留为下一次发布的证据。所有权和发布事实见构建保证。
构建一次,审阅后发布
章节:构建一次,审阅后发布此 API 已包含在公开 0.3.1 包中。
构建一次,检查报告,然后发布同一个候选文件。审阅后再运行一次普通构建会创建另一个候选包;预备发布使实际 APKG 始终与已审阅报告对应。
use ankiforge::{BuildOptions, Note, Project};
fn main() -> Result<(), Box<dyn std::error::Error>> { let mut project = Project::new("spanish")?.default_deck("Spanish"); project.add("es:hola", Note::basic("hola", "hello"))?; project.build(BuildOptions::to("spanish-original.apkg"))?;
let mut next = Project::new("spanish")?.default_deck("Spanish"); next.add("es:hola", Note::basic("hola", "hello; hi"))?; let prepared = next.prepare_publication( BuildOptions::to("spanish-reviewed.apkg").update_from("spanish-original.apkg"), )?; println!("{:?}", prepared.report().comparison()); if prepared .report() .comparison() .is_some_and(|report| report.policy().allows_publication()) { let output = prepared.publish()?; println!("{}", output.artifact().path().display()); } Ok(())}在固定修订的源码目录中,把上面的完整程序保存为 anki_forge/examples/docs_publication.rs(本指南新增该示例;1199196 中未包含它),然后运行:
cargo run --locked -p ankiforge --example docs_publication示例在仓库根目录写出 spanish-original.apkg,并在策略允许发布时写出 spanish-reviewed.apkg。预备阶段不发布到目标。批准分发前,应审阅发现项并测试客户端导入;此示例只在策略允许时自动发布这次简单内容编辑。基准测试未测量该流程。