关于 wiki-dedup
wiki-dedup 用于发现并处理 Obsidian wiki 中的重复页面,支持只读审计、确认后合并和高置信度自动合并,并为破坏性编辑设置保护措施。
主要功能
- 01基于标题和元数据筛选候选项
- 02支持审计、交互合并和自动合并
- 03选择规范页面并整合内容
- 04创建重定向页并改写全库链接
- 05写入前的 Git 快照保护
适用场景
- 01查找不同名称下描述同一概念的页面
- 02只检查潜在重复项而不修改 vault
- 03合并确认重复的页面并保留链接
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ ar9av/ obsidian-wiki --skill wiki-dedup安装后在 Claude Code 中输入/wiki-dedup确认已加载。
详细介绍
Wiki 去重 — 身份解析与页面级去重
查找并合并以不同名称介绍同一概念的 Wiki 页面。这是一项写入密集且可能造成破坏的技能——页面合并无法自动撤销。在合并模式下操作前,请谨慎处理并确认。
**遵循 llm-wiki/SKILL.md 中的检索原语表。**候选项检测阶段只使用 frontmatter 和标题(开销低)。只有确认候选页面对后,才打开完整页面正文。
开始之前
**写作规范:**起草或重写自然语言 Markdown 前,请阅读并遵循 llm-wiki/SKILL.md 中的 Writing Profile Resolution 部分。框架架构、来源信息、安全要求和特定操作要求优先。
WRITING.md 中的偏好仅适用于新起草或重写的自然语言 Markdown;请保留源内容和结构化记录。
- 解析配置 — 遵循
llm-wiki/SKILL.md中的配置解析协议(内联@name覆盖 → 从 CWD 向上查找.env→ 全局配置 → 提示用户设置)。这会提供OBSIDIAN_VAULT_PATH和OBSIDIAN_LINK_FORMAT。 - 阅读
index.md,获取包含单行描述和标签的完整页面清单。 - 简要阅读
log.md— 如果刚进行过一次去重操作,请记下已合并的内容。
模式
| 模式 | 标志 | 行为 |
|---|---|---|
| 审计 | (默认) | 仅报告候选项 — 不写入 |
| 合并 | --merge | 显示每一对已确认的页面,并在合并前请求确认 |
| 自动合并 | --auto | 以非交互方式合并所有高置信度页面对(score ≥ 0.90) |
如果用户未指定,请以审计模式运行,并在询问是否继续前先呈现发现结果。
第 1 步:构建页面注册表
对知识库中所有 .md 文件执行 glob(排除 _archives/、_raw/、.obsidian/、index.md、log.md、hot.md、_insights.md,以及 frontmatter 中包含 redirects_to: 的文件——这些文件已经是合并后的重定向存根)。
对每个剩余页面,从 frontmatter 提取:
node_id— 相对于知识库根目录的路径,不含.mdtitle— frontmatter 的title字段aliases— frontmatter 的aliases列表(可能不存在)tags— frontmatter 的tags列表category— 目录前缀
构建查找表:node_id → {title, aliases, tags, category, summary}。
第 2 步:检测候选页面对
针对注册表中的每一对页面,使用以下信号计算相似度分数:
2a. 标题相似度信号
| 信号 | 评估方法 | 最高贡献值 |
|---|---|---|
| 词元重叠 | 小写标题词元(按空格、连字符、下划线和标点拆分)的 Jaccard 相似度 | 0.65 |
| 编辑距离 | 小写标题的归一化编辑距离:1 - (edits / max(len_a, len_b)) | 0.40 |
| 子串包含 | 一个标题是另一个标题的子串(例如,“RSC” ⊂ “React Server Components”) | 0.50 |
| 别名交叉匹配 | 页面 A 的标题出现在页面 B 的 aliases 中,或反之 | 0.65 |
综合标题分数 = min(max(token_overlap, edit_distance, substring), 0.65) + alias_cross_bonus。
无需精确计算——对相似程度作出可靠判断即可。
**标题提取说明:**有些页面使用 YAML 块标量(title: >- 或 title: |)。当 title: 的值为 >-、>、| 或 |- 时,实际标题位于下一行缩进内容中——请从那里读取。切勿将字面字符串 >- 当作标题进行比较。
2b. 语义信号(低开销阶段)
| 信号 | 分值 |
|---|---|
category 目录相同 | +0.10 |
| 标签重叠 ≥ 3 个共同标签 | +0.15 |
| 标签重叠 ≥ 2 个共同标签 | +0.05 |
| 第一个标签(主导标签)相同 | +0.05 |
2c. 阈值
将综合得分 ≥ 0.75 的配对标记为候选项。得分达到 0.90 的配对为高置信度。
得分范围 → 置信度标签:
| 得分 | 标签 |
|---|---|
| ≥ 0.90 | HIGH — 几乎可以确定是同一概念 |
| 0.75–0.89 | MEDIUM — 很可能是同一概念,需核实 |
| 0.60–0.74 | LOW — 可能是缩写或特化概念;除非用户要求,否则跳过 |
只有 HIGH 和 MEDIUM 候选项会进入步骤 3。
2d. 快速退出规则
如果知识库少于 10 个页面,则跳过配对循环并报告“知识库太小,无法有效识别重复项”。如果知识库超过 500 个页面,则按每批 50 对处理候选项,并在批次之间暂停并报告进度。
步骤 3:语义判定
对每一对候选项(按得分降序排列):
- 完整阅读两个页面(完整阅读页面是合理的,因为候选项数量较少)。
- 判断:这些页面涵盖的是同一概念,还是不同概念?
指定以下三种判定之一:
| 判定 | 含义 |
|---|---|
merge | 同一概念——名称不同、使用缩写、别名或意外重复。可以安全合并。 |
keep-separate | 相关但不同——例如,“Server Actions”和“Server Components”是相关的 React 功能,但并非重复项。 |
needs-review | 存在歧义——内容大幅重叠,但也有实质性差异。标记出来交由用户决定。 |
为每项判定附上一条简短理由(一句话)。这会显示在报告和日志中。
步骤 4:审计报告
始终生成此报告,即使在合并/自动合并模式下也要生成(以便用户了解将执行的操作):
## Wiki Dedup Report
### High-Confidence Candidates (score ≥ 0.90): N pairs
| Score | Page A | Page B | Verdict | Reason |
|---|---|---|---|---|
| 0.95 | `concepts/rsc.md` | `concepts/react-server-components.md` | merge | "RSC" is the abbreviation; both pages cover identical material |
| 0.91 | `entities/vaswani-2017.md` | `references/attention-is-all-you-need.md` | keep-separate | One is a person stub, one is a paper reference |
### Medium-Confidence Candidates (score 0.75–0.89): N pairs
| Score | Page A | Page B | Verdict | Reason |
|---|---|---|---|---|
| 0.82 | `concepts/fine-tuning.md` | `concepts/finetuning.md` | merge | Same concept, hyphenation variant |
### Needs Human Review: N pairs
| Score | Page A | Page B | Reason |
|---|---|---|---|
| 0.78 | `concepts/agents.md` | `concepts/autonomous-agents.md` | Substantial overlap but "agents" may intentionally be broader |
### Summary
- Pages scanned: N
- Candidate pairs found: M
- Recommended merges: X
- Keep separate: Y
- Needs review: Z
在审计模式下,到此为止并询问:“运行 --merge 以交互方式合并建议的配对,或运行 --auto 以自动合并所有高置信度配对?”
步骤 5:合并
写入前快照——在首次写入文件之前,检查知识库本身是否为 Git 仓库根目录。仅仅是更大仓库的子目录并不符合条件:在此处运行 git add -A 可能会纳入无关文件。如果知识库不是独立的 Git 仓库,则静默跳过此步骤——不要催促,也不要建议运行 git init。
VAULT_REAL_PATH=$(cd "$OBSIDIAN_VAULT_PATH" && pwd -P)
VAULT_GIT_ROOT=$(git -C "$OBSIDIAN_VAULT_PATH" rev-parse --show-toplevel 2>/dev/null || true)
SNAPSHOT_SHA=""
if [ -n "$VAULT_GIT_ROOT" ] && [ "$VAULT_GIT_ROOT" = "$VAULT_REAL_PATH" ]; then
if git -C "$OBSIDIAN_VAULT_PATH" diff --quiet \
&& git -C "$OBSIDIAN_VAULT_PATH" diff --cached --quiet \
&& [ -z "$(git -C "$OBSIDIAN_VAULT_PATH" ls-files --others --exclude-standard)" ]; then
SNAPSHOT_SHA=$(git -C "$OBSIDIAN_VAULT_PATH" rev-parse HEAD)
else
if ! git -C "$OBSIDIAN_VAULT_PATH" add -A; then
echo "Pre-write snapshot failed; abort the skill without writing any vault files." >&2
exit 1
fi
if ! git -C "$OBSIDIAN_VAULT_PATH" commit -m "pre-wiki-dedup snapshot" --quiet; then
echo "Pre-write snapshot failed; abort the skill without writing any vault files." >&2
exit 1
fi
SNAPSHOT_SHA=$(git -C "$OBSIDIAN_VAULT_PATH" rev-parse HEAD)
fi
fi
干净仓库分支会刻意避免调用 git commit,因此“没有可提交的内容”不会被视为错误。如果 git add 或 git commit 失败,则在编辑知识库之前停止;绝不要在没有创建承诺的快照的情况下继续。
如果 SNAPSHOT_SHA 非空且此技能写入了文件,则在最终报告中包含该 SHA。若要撤销整个运行,在确认没有值得保留的后续更改后,用户可以运行:
git -C "$OBSIDIAN_VAULT_PATH" reset --hard "$SNAPSHOT_SHA"
git -C "$OBSIDIAN_VAULT_PATH" clean -fd
对于判定为 merge 的每一对(在合并或自动合并模式下):
在合并模式下:显示配对和判定,然后询问:“将 [Page A] 合并到 [Page B]?(yes/skip/review)”。除非回答 yes,否则跳过。
在自动合并模式下:仅在无需询问的情况下处理 HIGH 置信度(score ≥ 0.90)的合并项。
5a:选择规范页面
按以下顺序应用决胜规则,直到选出胜者:
- 更多指向该页面的双向链接——在知识库中用 grep 搜索
[[node_id]]引用;数量较多者胜出 - 内容更丰富——页面正文更长(行数更多)者胜出
- 来源更多——
sources:列表更长者胜出 - 标题长度——标题更长、更具描述性者胜出(例如,“React Server Components”优于“RSC”)
- 按字母顺序——标题靠前者胜出
规范页面即为保留页面。另一个页面成为次要页面(其内容会并入规范页面,随后替换为重定向存根)。
5b: 将内容合并到规范页面
阅读两个页面。更新规范页面:
aliases:— 添加次要页面的标题及其所有别名(不得重复)tags:— 合并两个标签列表(去重,最多保留 5 个领域标签 + 系统标签)sources:— 合并两个来源列表(去重)relationships:— 合并两个关系列表(按目标去重,优先采用带类型的条目)base_confidence— 根据合并后的来源并按照llm-wiki/SKILL.md中的公式重新计算updated— 设置为当前时间summary:— 如果次要页面扩展了内容范围,则重写摘要以涵盖合并后的范围- 正文内容 — 合并次要页面中的独有章节和要点。不要直接追加,应整合内容。避免重复规范页面中已有的说法。需要综合推断时使用
^[inferred]标记。 provenance:— 合并后重新计算
5c: 在次要页面路径写入重定向存根
---
title: <secondary page title>
redirects_to: "[[<canonical node_id>]]"
aliases: [<secondary aliases>]
category: <secondary category>
tags: []
created: <secondary original created>
updated: <obsidian-wiki memory now>
---
This page has been merged into [[<canonical page title>]].
redirects_to: 字段会告知读取此页面的技能应跟随重定向,而不是将其视为内容。
5d: 在整个知识库中重写 wikilink
在整个知识库中搜索所有指向次要 slug 的链接:
[[secondary-slug]]→[[canonical-slug]][[secondary-slug|display text]]→[[canonical-slug|display text]]- 如果
OBSIDIAN_LINK_FORMAT=markdown:[text](../path/to/secondary.md)→[text](../path/to/canonical.md)
安全规则:
- 不得重写代码块(``` 围栏或
inline code)中的内容 - 不得重写重定向存根本身(这是唯一应保留旧 slug 以便识别的位置)
- 不得使用
rm或具有破坏性的 shell 操作——只能使用 Edit/Write 工具 - 一次只重写一个文件;继续处理下一个文件之前,先验证当前文件
- 如果文件中没有匹配项,则跳过该文件
5e: 更新跟踪文件
index.md 和 hot.md 将由第 6 步中的 memory sync 调用重新生成——页面遍历器会跳过重定向存根,因此次要页面的条目会消失,规范页面则会自动采用合并后的摘要。
.manifest.json — 对于次要页面的来源条目:在每个条目中添加 "merged_into": "<canonical node_id>"。对于规范页面:将次要页面的 pages_created 和 pages_updated 列表合并进来。
5f: 最终检查
所有合并完成后,在知识库中搜索是否还有剩余的 [[secondary-slug]] 引用(存根文件除外)。如果仍有,请报告——重写步骤可能遗漏了非标准链接格式。
第 6 步:记录
一次加锁调用会写入日志行,并协调更新索引和热缓存:
obsidian-wiki memory sync DEDUP \
mode=<audit|merge|auto-merge> pages_scanned=<N> pairs_found=<M> \
merged=<X> kept_separate=<Y> needs_review=<Z> \
wikilinks_rewritten=<W> \
--takeaways "Merged N duplicate pairs; canonical pages updated."
在审计模式(未合并任何内容)下,改用 obsidian-wiki memory log DEDUP ...——只读运行不得重写索引或热缓存。
切勿手动编辑 index.md、log.md 或 hot.md——该命令会获取锁,防止并行写入者丢失你的更新。
完整流程请参见 .skills/llm-wiki/references/MEMORY.md。
重定向存根处理
其他技能应按以下方式处理重定向存根:
wiki-export— 跳过 frontmatter 中包含redirects_to:的页面;这些页面不是内容节点wiki-query— 如果搜索命中重定向存根,则跟随redirects_to:并改为读取规范页面wiki-lint— 验证每个redirects_to:wikilink 是否指向现存的非存根页面(重定向链——存根指向存根——属于错误)cross-linker— 将重定向存根视为非目标;切勿添加指向存根页面的新[[wikilink]]
提示
- 始终先审计。 即使在自动合并模式下,也会显示审计报告。信任结果之前,请先阅读报告。
- 最后检查
needs-review。 这些是棘手的情况,不要和明显可以合并的条目一起批量处理。 - 缩写是最常见的情况。 “GPT” / “GPT-4” / “GPT4”、“RSC” / “React Server Components”、“LLM” / “Large Language Models”——这些在子串包含匹配上的得分很高,几乎总是可以安全合并。
- 不同版本不算重复项。 “GPT-3”和“GPT-4”相关,但各不相同。“fine-tuning”和“fine-tuning-llms”也可能各不相同(技术与特定应用)。
- 去重后运行
cross-linker。 重定向存根会让图谱处于略微不一致的状态。Cross-linker 会让它更完善。
写入知识库后刷新 QMD
QMD 是搜索索引,而非事实来源。如果 $QMD_WIKI_COLLECTION 为空或未设置,请跳过此步骤。仅在此技能写入或重写知识库 Markdown 后运行。如果 QMD 刷新失败,请勿回滚知识库更改;请单独报告 QMD 状态。
如果已设置 $QMD_CLI,请使用它;否则使用 qmd。
${QMD_CLI:-qmd} update
如果输出提示需要向量,或嵌入可能已过时,请运行:
${QMD_CLI:-qmd} embed
使用以下任一方式验证集合:
${QMD_CLI:-qmd} ls "$QMD_WIKI_COLLECTION"
或者,在已知具体页面路径时:
${QMD_CLI:-qmd} get "qmd://$QMD_WIKI_COLLECTION/<page>.md" -l 5
记录以下一项:
QMD refreshed: update + embed + verifiedQMD refreshed: update only + verifiedQMD skipped: QMD_WIKI_COLLECTION unsetQMD skipped: qmd CLI unavailableQMD failed: <short error summary>