mcpField
W
wiki-dedup

wiki-dedup

github.com/ar9av生产力3.6k1.7k 次安装1 天前更新
代码仓库

关于 wiki-dedup

wiki-dedup 用于发现并处理 Obsidian wiki 中的重复页面,支持只读审计、确认后合并和高置信度自动合并,并为破坏性编辑设置保护措施。

主要功能

  1. 01基于标题和元数据筛选候选项
  2. 02支持审计、交互合并和自动合并
  3. 03选择规范页面并整合内容
  4. 04创建重定向页并改写全库链接
  5. 05写入前的 Git 快照保护

适用场景

  1. 01查找不同名称下描述同一概念的页面
  2. 02只检查潜在重复项而不修改 vault
  3. 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;请保留源内容和结构化记录。

  1. 解析配置 — 遵循 llm-wiki/SKILL.md 中的配置解析协议(内联 @name 覆盖 → 从 CWD 向上查找 .env → 全局配置 → 提示用户设置)。这会提供 OBSIDIAN_VAULT_PATH 和 OBSIDIAN_LINK_FORMAT。
  2. 阅读 index.md,获取包含单行描述和标签的完整页面清单。
  3. 简要阅读 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 — 相对于知识库根目录的路径,不含 .md
  • title — 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.90HIGH — 几乎可以确定是同一概念
0.75–0.89MEDIUM — 很可能是同一概念,需核实
0.60–0.74LOW — 可能是缩写或特化概念;除非用户要求,否则跳过

只有 HIGH 和 MEDIUM 候选项会进入步骤 3。

2d. 快速退出规则

如果知识库少于 10 个页面,则跳过配对循环并报告“知识库太小,无法有效识别重复项”。如果知识库超过 500 个页面,则按每批 50 对处理候选项,并在批次之间暂停并报告进度。

步骤 3:语义判定

对每一对候选项(按得分降序排列):

  1. 完整阅读两个页面(完整阅读页面是合理的,因为候选项数量较少)。
  2. 判断:这些页面涵盖的是同一概念,还是不同概念?

指定以下三种判定之一:

判定含义
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:选择规范页面

按以下顺序应用决胜规则,直到选出胜者:

  1. 更多指向该页面的双向链接——在知识库中用 grep 搜索 [[node_id]] 引用;数量较多者胜出
  2. 内容更丰富——页面正文更长(行数更多)者胜出
  3. 来源更多——sources: 列表更长者胜出
  4. 标题长度——标题更长、更具描述性者胜出(例如,“React Server Components”优于“RSC”)
  5. 按字母顺序——标题靠前者胜出

规范页面即为保留页面。另一个页面成为次要页面(其内容会并入规范页面,随后替换为重定向存根)。

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 + verified
  • QMD refreshed: update only + verified
  • QMD skipped: QMD_WIKI_COLLECTION unset
  • QMD skipped: qmd CLI unavailable
  • QMD failed: <short error summary>

评论

支持 Markdown