间隔重复复习系统。扫描Obsidian原子卡片,用FSRS-6算法安排复习,AI出题+评分。当用户说"review"、"复习"、"间隔重复"、"复习卡片"、"今天复习什么"时触发。
间隔重复复习系统 — 用 FSRS-6 算法复习 Obsidian 中的原子卡片(石头)。
研究 → /note 存卡 → /review 复习 → 知识巩固
| Skill | 职责 | 输出 |
|---|---|---|
/note | 知识沉淀 — 研究摘要 + 原子卡片 | Obsidian 卡片组 |
/review | 知识巩固 — 间隔重复复习 | 本地 review_state.json |
/journal | 进度记录 — 做了什么 | 本地 journal + Obsidian journal |
$HOME/.claude/skills/review/$HOME/.agents/skills/review/$HOME/.claude/skills/review/scripts/fsrs_engine.py$HOME/.agents/skills/review/scripts/fsrs_engine.py${KM_REVIEW_STATE_PATH:-$HOME/.claude/skills/review/review_state.json}${KM_REVIEW_STATE_PATH:-$HOME/.agents/skills/review/review_state.json}下文所有命令里的两个占位按当前运行时替换:
<FSRS_ENGINE_PATH>
$HOME/.claude/skills/review/scripts/fsrs_engine.py$HOME/.agents/skills/review/scripts/fsrs_engine.py<REVIEW_STATE_PATH>
review_state.jsonKM_REVIEW_STATE_PATH默认状态文件位置与 installed skill 同目录:
~/.claude/skills/review/review_state.json~/.agents/skills/review/review_state.json如需自定义(例如跨机器同步、多 vault 隔离、测试隔离),通过环境变量覆盖:
export KM_REVIEW_STATE_PATH=/path/to/your/review_state.json
本 skill 内所有 fsrs_engine.py 的调用都会展开这个变量,自动使用自定义路径。不 export 时使用默认路径。
Claude Code 通常直接写 /review。Codex 可显式写 $review(或用自然语言让 Codex 按 skill 描述隐式匹配)。
/review # 扫描 + 复习(默认)
/review --mode=scan # 仅扫描新卡片,不复习
/review --mode=stats # 查看统计信息
/review --topic=多巴胺 # 只复习某主题
/review --limit=5 # 本次最多复习5张
每次 /review 自动执行(除 mode=stats)。
python3 <FSRS_ENGINE_PATH> <REVIEW_STATE_PATH> stats
从返回的 known_card_ids 得知已注册卡片。
唯一识别标准:frontmatter 含 type/atomic 标签 = 原子卡片。
obsidian search query="type/atomic"
从返回结果中,过滤出标签含 type/atomic 的文件 = 原子卡片候选。
如果用户指定了 topic,进一步过滤:文件名或后续读取的内容中包含 topic 关键词。
对比 known_card_ids,只保留未注册的新卡片。
对新卡片使用 CLI 逐个读取完整内容(仅当 Obsidian 未运行时回退 mcp__obsidian__read_multiple_notes),然后批量注册:
echo '<JSON>' | python3 <FSRS_ENGINE_PATH> <REVIEW_STATE_PATH> bulk_register
输入 JSON 格式:[{"id": "Cards/{title}.md", "title": "...", "content": "..."}]
注意:始终使用 bulk_register(stdin JSON),即使只有一张卡片。不要使用 register CLI 命令注册含特殊字符的卡片内容。
对每张新注册的卡片,并行调用:
obsidian property:set path="Cards/{title}.md" name="tags" value="{existing_tags},mastery/new" type=list
扫描完成 — 发现 N 张新卡片,卡片池共 M 张
如果 mode=scan,到此结束。
python3 <FSRS_ENGINE_PATH> <REVIEW_STATE_PATH> due --limit <limit> --new_limit <new_limit>
混合比例制:默认 new_limit = limit // 2(50% 新卡 + 50% 旧卡)。引擎会先取 new_limit 张新卡,剩余名额给 learning/review 旧卡。任一类不够时自动补给另一类。
如果用户指定了 topic,从到期卡片中过滤:
domain/ 标签 → 精确匹配 domain/{topic}回忆模式:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[1/8] 回忆模式
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
请凭记忆描述这张卡片的核心内容:
【多巴胺】编码动机而非快乐
提问模式: 从卡片内容中提取一个关键知识点,生成具体问题:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[1/8] 提问模式
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Kent Berridge 的研究区分了大脑中哪两套独立的奖励系统?
提问要求:
等待用户回答。
如果用户回答"淘汰"、"skip"、"retire"或类似表达,执行淘汰流程而非评分:
python3 <FSRS_ENGINE_PATH> <REVIEW_STATE_PATH> retire --id <card_id>
mastery/retired:obsidian property:set path="Cards/{title}.md" name="tags" value="{updated_tags_with_mastery/retired}" type=list
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
已淘汰:【卡片标题】
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
卡片保留在 Obsidian,不再出现在复习中。
对照卡片原文(content_snippet),从三个维度评估:
评分映射:
| Rating | 值 | 条件 |
|---|---|---|
| Again | 1 | 完全不记得 / 核心全错 |
| Hard | 2 | 方向对但关键细节缺失 >50% |
| Good | 3 | 核心知识点基本正确 |
| Easy | 4 | 完全准确含关键细节 |
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
评分:Good (3)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
正确:wanting vs liking 的区分
遗漏:Salamone 的补充发现(多巴胺敲除鼠仍享受甜食)
卡片完整内容:
> [展示 content_snippet]
下次复习:3天后 (2026-02-23)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Again/Hard 助记强化:当评分为 Again (1) 或 Hard (2) 时,在展示卡片完整内容之后,额外生成一段助记内容帮助用户加深理解:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
💡 助记强化
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[根据卡片内容,选择以下一种或多种方式帮助记忆:]
- **生活类比**:用日常经验类比抽象概念
- **具体例子**:举一个具体、生动的实例
- **对比记忆**:和容易混淆的概念做对比(什么是、什么不是)
- **因果链条**:用"因为A→所以B→导致C"串联核心逻辑
- **一句话浓缩**:把核心要点压缩为一句记忆钩子
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
助记内容要求:
记录评分:
python3 <FSRS_ENGINE_PATH> <REVIEW_STATE_PATH> record --id <card_id> --rating 3
如果用户对评分有异议(如"这个应该是 Hard"),用用户覆盖的评分重新调用 record。
每次 record 后,根据本次复习评分写回掌握等级到卡片 frontmatter:
| 标签 | 对应评分 | 含义 |
|---|---|---|
mastery/new | — | 从未复习 |
mastery/again | Again (1) | 完全不记得 |
mastery/hard | Hard (2) | 勉强想起,细节缺失 |
mastery/good | Good (3) | 核心掌握 |
mastery/easy | Easy (4) | 完全掌握 |
mastery/retired | 淘汰 | 已从复习池移除 |
操作步骤(可与下一张卡片的提问并行):
obsidian tags path="Cards/{title}.md"mastery/{level}obsidian property:set path="Cards/{title}.md" name="tags" value="{updated_tags}" type=list仅当 Obsidian 应用未运行且 CLI 报 connection refused 时回退 MCP
mcp__obsidian__manage_tags(remove + add 两步并行)。
继续下一张,直到所有到期卡片复习完或达到 limit。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
复习完成
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
本次复习:8 张卡片
评分分布:Again 1 | Hard 2 | Good 4 | Easy 1
平均记忆保持率:82%
薄弱卡片:
- 【消退学习】— Again, 明天复习
- 【前额叶皮层】— Hard, 2天后复习
下次复习:明天有 3 张卡片到期
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
记录 session 到状态文件:
echo '{"date":"2026-02-20","cards_reviewed":8,"ratings":{"Again":1,"Hard":2,"Good":4,"Easy":1},"avg_retrievability":0.82}' | \
python3 <FSRS_ENGINE_PATH> <REVIEW_STATE_PATH> record_session
python3 <FSRS_ENGINE_PATH> <REVIEW_STATE_PATH> stats
格式化输出:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
复习统计
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
卡片池:M 张(新 X / 学习中 Y / 复习中 Z)
今日到期:N 张
下次到期:YYYY-MM-DD
历史评分分布:
Again: XX | Hard: XX | Good: XX | Easy: XX
平均难度:X.X / 10
平均稳定性:X.X 天
总复习次数:XX 次
总会话数:XX 次
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
<REVIEW_STATE_PATH>(Claude Code / Codex 按运行时替换;如有覆盖,优先 KM_REVIEW_STATE_PATH)