1. “知识健康”到底是个什么问题
如果你和我一样,用本地 Markdown 笔记记了几年东西,知识库会慢慢长成一个失控的杂物间。我见过不少人的笔记目录是这样的:新建文档 2023-01、未命名 12、final_really_final_v3,同一个概念在十几个笔记里反复出现,链接指向的文件早就被改名或删掉了,还有一些笔记写了八百字开头之后就再也没动过。
这还不算什么。真正让人头疼的是,当你想基于这些积累做一个完整输出的时候,你会发现你不知道哪些笔记是可信的、哪些是过期的、哪些内容其实可以合并成一个更大的主题。你只知道“我记了很多”,但完全不知道“我记的东西健不健康”。
我管这个叫知识库的“健康度”问题。它和人体健康很像:关注体重不代表关注体脂,关注笔记数量也不代表关注知识状态。一个知识库健康与否,应该看它内部有没有断链、有没有重复、有没有陈旧信息、有没有孤立主题、有没有长期不更新的“僵尸笔记”,以及各主题之间的连通性。这些指标单独拎出来都不起眼,合在一起却能反映知识库的真实状态。
之所以想做一个专门关注“知识健康”的开源项目“玉衡(Yuheng)”,就是因为市面上常见的笔记工具,核心能力基本都集中在“记”和“找”这两个环节。你会看到各种花哨的块引用、双向链接、全文搜索,但几乎没有工具会告诉你:“你的笔记库里有 87 个断链”、“你有 23 篇笔记高度相似”、“有 41 篇笔记超过一年没更新了,其中 12 篇的内容大概率已经过时”。
玉衡想做的,恰恰是补上这个缺口:像体检一样扫描知识库,输出一份可读的健康报告,给出具体到文件路径的修复建议,并且持续追踪知识库的健康趋势。它不是一个新笔记软件,不抢现有工作流的戏份,而是安安静静地站在一边,告诉你这片“知识田”该浇水还是该除虫。
适合它的用户也蛮清晰:笔记积累超过几百篇的人、用 Markdown 或纯文本管理知识库的人、对“整理笔记”有长期需求但又不想靠纯手工维护的人、以及想给自己建一套“自动化内容质量保障体系”的折腾党。小白也能用它跑一次全库体检,哪怕只是看一眼报告,都会对“知识库健康”这个概念有完全不一样的认识。
2. 玉衡该怎么理解这个需求
2.1 名字的来历与产品定位
“玉衡”是北斗七星中的第五颗星,古人观星时用来定方向、辨季节。我起这个名字的时候想得很简单:一个知识库也应该有一颗“玉衡”,帮你测定方向——哪些内容在支撑你的知识体系,哪些内容已经偏航了。
先说说产品定位。我一开始有过几个选项:
- 做成 Obsidian 插件:和具体工具绑定太深,换个笔记软件就废了。
- 做成“笔记整理 AI 助手”:太重,不符合“轻量、透明、可解释”的初衷。
- 做成一个命令行工具 + 可读报告:跨平台、跨场景,任何用 Markdown/纯文本存知识的人都能用。
我最后选了第三条路。玉衡本质上是一个静态分析器,不是常驻后台的监控进程。它跑完一次,生成一份报告,剩下的事情交给用户去决策。这样做有好处:不干扰你写笔记时的节奏,不占内存,也不会偷偷扫描你的敏感内容,所有计算都在本地完成。
2.2 核心设计原则:不打扰、可解释、可行动
从第一版原型开始,我就给自己定了三条铁律。
第一条,不打扰。玉衡不做成常驻服务,也不在你写笔记时弹提示框。你需要它时手动跑一次,或者挂个定时任务让它自动跑。原因很简单:知识管理工具最大的敌人是用户的使用负担,负担一重,工具就会被弃用。
第二条,可解释。每一个健康指标都必须能回溯到具体的文件、具体的行、具体的规则。不能说“你的知识库不够健康”就完了,要说“你的笔记库中约 12% 的链接指向不存在的文件,其中最集中的三个目录是 xxx、yyy、zzz”。我拒绝在早期版本里引入“AI 判分”,因为 AI 打的分说不清楚,用户不放心,出错了也没法修。
第三条,可行动。报告里每个问题都要带修复建议。死链就告诉你是哪篇笔记指向哪个失效目标;重复内容就告诉你相似度有多高、建议保留哪篇;陈旧内容就标记出最后修改时间。光发现问题不能叫健康管理,能推动你去做整理才算。
2.3 技术选型与数据模型
技术栈上我选的是Python 3.10+,CLI 框架用 Typer,Markdown 解析用 python-frontmatter 加正则辅助,历史趋势数据存 SQLite,报告输出为 Markdown 文件。
为什么是 Python?因为这类“扫描文本 + 统计规则 + 生成报告”的场景,Python 的标准库和生态最省事。你不需要编译,改完代码立刻能跑,对贡献者来说门槛也低。Typer 会让你写 CLI 的体验非常舒服,参数解析、帮助文档、补全这些都不用自己造轮子。
数据模型其实很朴素,核心就是一张“文件信息表”和一张“链接关系表”:
@dataclass class DocInfo: path: str title: str word_count: int created_at: datetime | None modified_at: datetime | None tags: list[str] outbound_links: list[str] @dataclass class LinkEdge: source: str target: str link_type: str # wiki / md / url有了这两个基础结构,死链、孤岛、连通度、陈旧度这些指标都能算出来。SQLite 表结构也不复杂,存三类数据:文件快照、链接关系快照、历次扫描报告摘要。通过对比两次快照,就能看出知识库是在变好还是变坏。
3. 核心功能拆解与实现思路
3.1 知识库体检维度与评分模型
玉衡把“知识库健康”拆成了六个维度,每个维度都有明确的定义和检测方式。我第一次设计时有八个维度,后来砍到六个,因为发现多一个维度就多一层理解成本,用户反而不容易上手。现在的维度如下:
| 健康维度 | 指标定义 | 检测方式 | 典型问题 |
|---|---|---|---|
| 链接健康 | 链接总数中无效链接的占比 | 解析所有 Markdown 链接并检查目标是否存在 | 死链、指向改名文件的旧链接 |
| 内容冗余 | 相似度超过阈值的文档对数 | 计算正文的 SimHash 并做近似去重 | 重复笔记、同一素材反复抄写 |
| 结构完整 | 空文档、无标题文档、超长文档的占比 | 解析 Frontmatter 和正文结构 | 只有标题没有正文的“空壳笔记” |
| 时效状态 | 超过 N 个月未更新的文档占比 | 基于文件修改时间统计 | 过期信息、僵尸笔记 |
| 主题连通 | 孤立节点(无入链也无出链)的占比 | 构建有向图,计算节点度数 | 散落的碎片内容,没有上下文 |
| 元数据质量 | 缺少 Frontmatter 或 Tags 的文档占比 | 检查 YAML Frontmatter | 无法聚合、检索的信息孤岛 |
每个维度先算出“问题发生率”,再用加权公式转成 0~100 分。总分不是简单平均,而是链接健康占 30%、内容冗余 20%、结构完整 15%、时效状态 15%、主题连通 10%、元数据质量 10%。链接健康权重最高,是因为知识库一旦断链,各种自动化流程和阅读路径都会受影响,修复成本也最高。
这个评分模型我调过很多轮。最初版本是越细越好,每个维度都给了比较平均的权重,结果报告出来后用户反馈“哪里都有问题,不知道先弄哪个”。后来改成突出核心维度,报告里也按“严重 / 中等 / 轻微”分级展示,这直接解决了一半的“看过报告不知道该干嘛”的问题。
3.2 死链、孤岛、重复、过期的检测实现
死链检测。Markdown 里的链接主要有三种形态:[文字](相对路径.md)、[[双链]]、以及裸路径引用。我做了两层解析:第一层用正则把所有链接模式抓出来,第二层对相对路径做“规范化解析”,得到目标绝对路径,再判断文件是否存在。
import re from pathlib import Path WIKI_LINK_RE = re.compile(r"\[\[([^\]|#]+?)(?:#[^\]|]*)?(?:\|[^\]]*)?\]\]") MD_LINK_RE = re.compile(r"\[[^\]]*\]\(([^)]+)\)") def extract_markdown_links(text: str, base_dir: Path): links = set() for raw in WIKI_LINK_RE.findall(text): target = resolve_wiki_target(raw.strip(), base_dir) if target: links.add(target) for raw in MD_LINK_RE.findall(text): if raw.startswith("http://") or raw.startswith("https://"): continue target = (base_dir / raw).resolve() links.add(target) return links这里有个细节:[[链接]]里的目标可能不带.md后缀,也可能带/分隔路径,解析时要尝试几种常见补全方式——先补.md,找不到就补/index.md,再找不到再看有没有同名目录。我们讨论过要不要支持“智能匹配同名不同目录文件”,最后还是没做。启发式匹配越多,误报率越高,我宁愿多报几个“可疑链接”让用户自己确认,也不能悄悄把坏链接修成错误目标。
孤岛检测。我构建了一张有向图,节点是全部 Markdown 文件,边是链接关系。每个节点的“入度”是别的笔记通过链接指向它的次数,“出度”是这篇笔记指向其他笔记的次数。入度和出度都为零的节点,就是孤立节点。这个计算用纯 Python 跑,几千个文件也就几秒钟,不需要额外引入图数据库。
重复检测。完全重复的笔记好办,算个 MD5 就行。真正麻烦的是“内容高度相似但措辞不同”的笔记。我用的是 SimHash 加汉明距离,先把正文做分词,然后计算每个文档的 64 位指纹,两两比较时看汉明距离是否小于某个阈值。SimHash 的好处是对局部修改不敏感,两篇笔记如果有 80% 的内容重叠,它们的指纹距离会非常近。阈值我默认设 3,实际使用中可以根据自己笔记的风格调。
def simhash(text: str, hash_bits: int = 64) -> int: weights = [0] * hash_bits tokens = tokenize(text) for token in tokens: h = hash_token(token) for i in range(hash_bits): mask = 1 << i if h & mask: weights[i] += 1 else: weights[i] -= 1 fingerprint = 0 for i in range(hash_bits): if weights[i] > 0: fingerprint |= 1 << i return fingerprint这个算法是经典的 SimHash 实现思路,不是我的原创,但在“判断笔记是否重复”这个场景下特别好用。需要注意的是中文切词不能按空格来,早期版本直接用 jieba 分词,后来为了减少依赖,换成了“按标点拆句 + 提取 2~4 个连续字符的 n-gram”方案。实测下来对中文内容的重复检测效果完全够用,而且少了 jieba 这个重依赖,安装体积小了不少。
过期检测。这个最简单,直接读取文件的st_mtime,和当前时间做差。但要注意一个矛盾:有些笔记是“永久笔记”,内容长期不变反而说明它稳定健康。我给出的补偿机制是:如果文档带有status: evergreen或status: done这样的 Frontmatter 标记,就跳过过期判定。也就是说,玉衡不会一刀切地认为“不更新 = 有问题”,而是允许你告诉它哪些内容不需要频繁更新。
3.3 报告生成与趋势追踪
扫描完之后,玉衡会在指定输出目录(默认是知识库根目录下的.yuheng/)生成三样东西:
report-YYYY-MM-DD.md:完整的健康报告。health.json:机器可读的指标摘要,方便接入其他工具。history.db:SQLite 数据库,记录历次扫描的核心指标。
报告的结构我花了不少心思。为了让用户快速定位最要紧的问题,报告开头是一个“总体结论”,比如“本次体检综合评分 72 分,较上次下降 3 分,主要原因是链接健康度从 88 分降到了 79 分”。然后是详细的维度表格、问题清单、修复建议。问题清单里每条都标注文件路径和具体位置,路径直接写成代码格式,方便复制进编辑器跳转。
趋势追踪往往被人忽视,但我觉得这是“健康管理”和“一次体检”的根本区别。身体体检如果年年做,真正有价值的不是单次报告,而是曲线:血压是稳了还是在爬升?知识库也一样。你跑了第一次扫描,把评分基线建立起来,然后再过一个月跑第二次、第三次,看到健康度曲线往上走,这种反馈带来的整理动力,比任何“待办清单”都管用。
3.4 自动化修复与“养护清单”
有些人以为健康报告看完就该去手动修。但我觉得能自动化的就不要让人做重复劳动。玉衡提供了一组半自动修复命令,每条命令在执行前都会把“将要对哪些文件做什么修改”列出来,你需要确认后才执行。这是很刻意的设计:知识库是你的私有资产,工具不能代替你做判断,只能在可信的场景下帮你动手。
目前支持的修复动作:
- 修复死链:当链接目标不存在,但发现“改名前的旧名称恰好对应某个现有文件”时,自动把链接文字更新为新文件名。
- 清理重复:把重复度超过阈值的文档列出来,按修改时间排序,建议删旧留新;不直接删除,只生成待删除清单。
- 标记过期:给超过 12 个月未更新、且没有
status标记的文档自动插入status: stale字段。 - 补充 Frontmatter:给缺失标题、缺失 tags 的文档自动生成最小化 Frontmatter。
这些修复动作全部是幂等的,跑两遍不会产生二次副作用。日志也写得很明确,每个文件动了哪里,都会在终端里打出来。
我还设想了一个“养护清单”功能:把最值得优先处理的十个问题汇总成一个todo.md,放在知识库根目录。你可以把它当作笔记打开,一勾一个处理,处理完跑一次玉衡,评分涨了,成就感也有了。这个功能本质上是用“任务管理”的思路来驱动知识库整理,实测下来比直接扔几百个问题清单给人要有效得多。
4. 实操:把玉衡跑起来
4.1 安装与初始化配置
安装很简单,如果 Python 环境已经准备好了,直接:
pip install yuheng需要Python 3.10或更高版本。装完先跑一下yuheng init,命令会在当前目录生成一个配置文件,默认文件名是yuheng.yaml。
yuheng init yuheng config --show配置文件长这样:
knowledge_base: root: ~/Documents/Notes # 你的笔记根目录 exclude_dirs: - .obsidian - .git - node_modules - .yuheng extensions: - .md scoring: weights: link_health: 30 content_redundancy: 20 structure_completeness: 15 freshness: 15 connectivity: 10 metadata_quality: 10 stale_after_months: 12 report: output_dir: .yuheng include_suggestions: trueexclude_dirs这行一定要留意。如果你用 Obsidian,.obsidian目录里全是配置和缓存文件,不排除掉它们,扫描结果会非常难看。类似的还有.git,里面塞满了历史快照,纯属噪声。如果你有其他二进制资源目录,比如附件图片目录,建议也加进去,玉衡目前只关注 Markdown 文本文件,不会去解析 PDF 或图片。
4.2 执行一次全库体检
初始化后,跑一次完整扫描:
yuheng scan如果你只改了少量文件,想快速看有没有新问题,可以加--incremental参数。增量模式会通过对比文件修改时间,只重新扫描变过的文件。第一次全量扫描可能比较慢,我拿一个五千多篇笔记的库测过,大概需要 20 秒左右,主要是 SimHash 计算会吃一点 CPU。如果是几百篇的规模,几秒钟就跑完了。
扫描结束后,终端会显示最简单的汇总:
本次扫描共发现 1234 个文件,4892 条链接。 综合健康评分:76.4 / 100 链接健康:68.2 内容冗余:81.0 结构完整:88.5 时效状态:72.3 主题连通:79.1 元数据质量:70.8 报告已生成:.yuheng/report-2025-03-09.md第一次跑完报告以后,我的建议是:不要急着修问题。花半小时把报告从头到尾翻一遍,感受一下自己的知识库在全貌上是什么状态。很多人在这个环节会有一种“啊,原来我的库长这样”的顿悟感。这时候你再决定先动哪一块,比工具引导你动哪一块更符合你的真实需求。
4.3 看懂你的健康报告
报告的主体分四个部分:总体概览、维度明细、问题清单、修复建议。我们挑一段实际报告来看。
## 2. 维度明细 ### 2.1 链接健康(评分 68.2) - 有效链接:4721 条 - 无效链接:171 条 - 无效链接比例:3.5% 其中,指向“疑似已改名文件”的链接有 28 条。最常见的失效目标集中在: - `notes/projects/旧项目复盘.md` - `notes/ideas/产品灵感笔记.md`链接健康这块,报告里会直接把无效目标路径列出来。你一眼就能看到哪些链接指向不存在的文件,然后可以去笔记里搜“旧项目复盘”这几个字,把链接文字改成现在实际存在的文件名。
再看重复内容部分:
### 2.2 内容冗余(评分 81.0) 发现 13 组高相似度文档,其中 3 组相似度超过 90%。建议优先处理: - `notes/idea/关于本地优先软件的想法.md` 与 `notes/pkm/本地优先工具思考.md` 相似度 94%,最近修改时间分别为 2024-03-02 和 2024-12-18 建议:保留后者,将前者的差异部分合并进去后删除。这里有个小技巧:玉衡列出的“建议保留”是根据修改时间推算的,默认保留较新的那篇。但你自己的真实情况未必如此,可能旧笔记里有一段早就忘了的深入思考,反而更有价值。所以报告里只给建议,不自动删,说实话这是有意为之。
4.4 定时体检与 CI 集成
如果你希望知识库健康度能长期被跟踪,定时任务是个好选择。macOS/Linux 下用 cron 一行就能搞定:
# 每周一早上 9 点跑一次体检 0 9 * * 1 cd /path/to/notes && /usr/local/bin/yuheng scan --quiet--quiet是给脚本用的模式,不输出彩色日志,只写报告文件。Windows 上可以开“任务计划程序”,或者直接用 GitHub Actions。如果你的笔记仓库本来就推到 Git 上,我推荐用 GitHub Actions 跑每周体检,这样健康趋势直接跟着仓库走。
name: weekly-knowledge-health-check on: schedule: - cron: "0 9 * * 1" jobs: scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.12" - run: pip install yuheng - run: yuheng scan --quiet - run: git add .yuheng && git commit -m "weekly health report" --allow-empty这套东西跑起来之后,你就拥有了一份随仓库更新的知识库健康档案。想回看三个月前知识库是什么状态,翻 Git 历史即可。
5. 常见问题与排查技巧实录
5.1 扫描速度与增量更新
第一次全量扫描偏慢,是最常见的抱怨。五千篇笔记 20 秒,对命令行工具来说不算慢,但用户体感上会觉得“卡了一下”。如果笔记量更大,比如几万篇,我建议把扫描拆成分批任务。玉衡没有内置分布式能力,但好在它支持对子目录单独扫描,你完全可以手动拆成几个指定目录跑。
增量更新的逻辑很简单:每份文件扫描后会在history.db里记录mtime。下次扫描时,文件mtime没变就跳过。但这个机制依赖系统文件时间戳,如果你用 Git 管理笔记并且git checkout切换分支,所有文件的时间戳都可能会变,导致增量失效,触发一次全量重扫。这不是 bug,是文件系统的正常行为,知道就好。遇到这种情况,直接跑一次全量扫描即可,不用做额外操作。
5.2 误报处理与规则调优
误报率是这类工具的命门。我总结过几类典型误报:
- 图片和附件路径误报为死链:比如
。玉衡默认只认.md扩展名,但链接到图片的路径,如果你希望它检查,却不想把图片本身当文档统计,就需要在配置里明确让图片路径“跳过存在性校验”。 - 锚点链接误报:
[[note#章节]]指向的章节名变了,文件还在,链接逻辑上也还成立。玉衡默认不校验锚点是否存在,只校验文件是否存在,原因很简单:解析标题和锚点的对应关系,复杂度会指数级上升。 - 大小写问题:Windows 路径不区分大小写,而 Linux 区分。如果你在 Windows 上建库、Linux 上跑扫描,可能会出现一批“假死链”。玉衡提供了一个
case_sensitive: false配置项,在跨平台场景下把它关掉能消除这类误报。
误报本身不可怕,可怕的是误报率太高导致用户不再信报告。所以我的建议是:宁可配置项多一些,也要让用户有能力把规则调到自己信任的状态。
5.3 中文内容与编码问题
中文场景有几个特有的坑。先说编码。Markdown 文件大多数是 UTF-8,但有些老笔记是 GBK 或 GB18030 编码,用默认方式打开会直接报错。玉衡在读取文件时会先尝试 UTF-8,失败就尝试 GB18030,再不行就跳过并记录“不可读文件”。这不算完美方案,但起码不会让整个扫描挂掉。
再说分词和相似度。SimHash 的效果强依赖 token 的切分方式,英文按空格切分很自然,中文如果整句不切,就会导致相似的句子因为字符顺序差异被判定为不相似。玉衡目前的做法是提取 2-gram 和 3-gram 的字符序列组合。举个例子,“知识健康检测”和“检测知识健康”这两个短语,虽然词序不同,但 2-gram 重叠度其实很高,SimHash 识别起来也不难。如果你觉得重复检测不够敏感或过度敏感,可以在配置里调simhash_threshold。
5.4 与不同笔记工具协作的注意事项
玉衡不绑定某个笔记软件,但不同工具的目录结构和文件规范会影响扫描结果。
如果你用的是带.obsidian目录的本地笔记库,记得把它排除掉。如果你用的是某些带数据库的笔记工具,核心内容是一个很大的数据库文件而不是.md文本,那玉衡目前对它无能为力,因为玉衡只处理纯文本 Markdown。这是刻意保持的边界,我不想为了兼容数据库格式而牺牲工具本身的简单性。
如果你在笔记里用了很多自定义的魔法语法,比如#todo、%%,==高亮==这些,玉衡通通不认识,但不影响它工作。它只关心链接、Frontmatter 和文件时间,其它内容默认当作普通文字处理。
6. 我在使用中的一些体会
6.1 健康度从 69 分到 87 分的真实变化
我自己有一个记了三年的知识库,启动这个项目之前,第一次扫描的综合评分只有 69 分。最扎眼的数据是:171 条死链、23 组高相似文档、14 篇超过一年没动的“僵尸笔记”,以及 31 个孤立节点。
我当时没有立刻去修复所有问题,而是按照报告里“严重程度”排序,先处理了死链最集中的三个目录。这三个目录里有大量“原来写过但后来改名了”的笔记,链接还指向老名字。玉衡列出的“疑似改名文件”帮了大忙,我只需要确认映射关系,然后批量执行yuheng fix broken-links --apply。光这一步,链接健康分就从 68 跳到了 86。
后来我把重复文档一篇篇处理完,再给所有“永久性笔记”打上status: evergreen标记,评分稳定在了 87 分上下。没有哪个单次操作是惊天动地的,但每次跑完扫描看到曲线往上走一点,确实比任何提醒事项都更能维持整理动力。
有了这段实操经历后,我才更加确信:知识管理这件事,真正稀缺的不是“多记一笔”的冲动,而是“知道现状在变好”的反馈。玉衡就是这个反馈机制的载体。
6.2 下一步想做的功能与社区协作方向
这个项目目前还很轻,我接下来的计划有几个方向。一是做语义相似度检测,用本地 embedding 模型替代 SimHash,这样能识别“表述完全不同但含义高度重合”的笔记,但前提是模型要够轻、能在普通电脑上跑起来,隐私和安全我是不会让步的。二是做“知识结构图”可视化,把孤岛节点和聚类结果画成一张图,让用户一眼看到自己的知识版图长什么样。三是完善规则库,把更多笔记风格纳入兼容范围。
说到底,玉衡的定位不是“终极解决方案”,而是一个可以按自己需求揉捏的基础工具。如果你也想贡献规则、插件或者新的体检维度,欢迎直接在仓库里开 Issue 或 PR。做过类似工具的人都知道,一个人能维护的规则始终有限,一群人的笔记习惯凑在一起,这个工具才能真正长成大家都顺手的样子。
如果你现在正面对一个越积越乱的知识库,我建议今晚就跑一次玉衡,先别想着怎么改,就看看报告。很多时候,看见问题的瞬间,解决路径自己就会浮现出来。