Lint 审计: 给你的数字大脑做一次体检
给你的数字大脑做一次体检
Lint 审计:让知识系统保持健康的完整操作手册
作者:一只阿木木 | 数字大脑摆渡人 🌊
前四篇,我们建好了架构,写好了规则,学会了提取。
你的
/wiki里已经有了几十个页面, 知识网络开始生长,系统开始运转。但今天,我要告诉你一件没人喜欢听的事:
你的系统,正在悄悄生病。
不是因为你做错了什么。 是因为所有知识系统,都会随着时间老化。 而大多数人,从来不给它做体检。
一个让我后背发凉的发现
我记得那天清晨,我打开系统,提了一个我觉得很基础的问题:
"RAG 和向量搜索的关系是什么?"
系统的回答,让我愣住了——
它给了我两个完全相反的答案,分别来自两个不同的 wiki 页面,都标注着"置信度:high"。
一个页面说:向量搜索是 RAG 的核心基础设施,缺少向量数据库,RAG 无法运行。
另一个页面说:在个人知识库规模下,LLM Wiki 证明了向量搜索并不是必须的,纯 Markdown 索引就足够了。
两句话,都是"对的"——只是在不同语境下。
但我的系统,把它们当成了同级别的绝对真理,并排放在那里,没有任何一个标记说它们存在张力。
这就是典型的「知识幻觉固化」——
不是 AI 临时编造了一个错误答案,而是两个有条件的真理,被系统当成了无条件的事实,并且互相矛盾地共存着。
这件事让我意识到:
一个没有定期体检的知识系统,不只是"不够好",它可能在某些时刻主动误导你。
这一篇,我们来谈谈如何防止这件事发生。
第一部分:为什么知识系统会"生病"?
要理解为什么需要 Lint,先要理解知识系统退化的根本原因。
1 有一个对 LLM Wiki 模式值得认真对待的批评:因为 LLM 将来源压缩为 wiki 页面,存在幻觉被固化为"事实"的风险。在纯 RAG 系统里,一个错误答案只是一个错误答案。但在 LLM Wiki 里,一个小误解可能会悄悄传播到关联页面。这正是 Karpathy 强调 Lint 步骤的原因——定期审计。
但幻觉传播,只是系统生病的原因之一。
知识系统退化,有四种主要的"病",每一种都有不同的症状、危害和治疗方式:
病 1:幽灵链接病(Dead Links)🔗
什么是它:
你的 wiki 页面里有 [[某个概念]] 的链接,但那个页面已经被重命名、移动或删除了。链接还在,但指向的东西不存在了。
为什么会发生:
你在迭代 Schema 的时候重新命名了几个页面。或者 AI 在创建链接时打错了目标页面的名字(kebab-case 里差一个字母)。
危害:
3 Lint 操作运行结构性检查:孤儿页面(wiki 中存在但 schema 中未定义的文件)、缺失页面(schema 中有 slug 但没有对应文件)、损坏的交叉引用([[slug]] 链接指向不存在的页面)。
当你提问时,AI 想要追踪这条链接,结果撞上一堵墙。它可能开始用通用知识填补这个空白——这正是你最不想要的。
治疗: 程序化扫描,秒级修复。
病 2:孤儿页面病(Orphan Pages)🏝️
什么是它:
2 定期健康检查应当寻找:页面之间的矛盾、已被更新来源取代的过时声明、没有入链的孤儿页面、被提及但缺少独立页面的重要概念、缺失的交叉引用,以及可以通过网络搜索填补的数据缺口。
孤儿页面,就是那些"没有任何其他页面链接到它"的页面。
为什么会发生:
新建了一个页面,但当时没有及时在其他相关页面里添加反向链接。时间久了,这个页面就成了一个信息孤岛。
危害:
你的 AI 在回答问题时,是从 index.md 出发,通过链接关系找到相关页面的。2两个特殊文件帮助 LLM(和你)随着 wiki 成长来导航。它们有不同的用途:index.md 是内容导向的,是 wiki 中所有内容的目录——每个页面都列出了链接、一行摘要和可选的元数据。按类别组织。LLM 在每次 Ingest 时更新它。在回答查询时,LLM 先读取索引找到相关页面,然后深入其中。
一个孤儿页面,在这个导航体系里是隐形的。它存在,但永远不会被找到。里面的知识,等于消失了。
治疗: 程序化扫描发现孤儿,语义审计决定是保留并重新链接,还是归档。
病 3:知识矛盾病(Contradictions)⚡
什么是它:
两个不同的页面,对同一个问题给出了互相冲突的答案,但都没有标注这个矛盾的存在。
为什么会发生:
3 当你添加一个来源,它对某个概念有显著更新时,路由步骤应该捕捉到它并触发重新综合。但如果该页面的 schema 描述模糊或不一致,路由 LLM 可能无法识别其相关性,导致页面变得陈旧。定期的 Lint 检查和周期性的全量重新摄入可以缓解这个问题,但需要主动维护。
简单说:你摄入了新素材,但旧页面没有被正确更新,新旧知识开始打架。
危害: 这是最危险的一种病。AI 不知道该相信哪个,可能给你一个置信度虚高的混合答案。你也不知道该信哪个,系统失去了作为「可靠知识源」的基本价值。
治疗: 必须用 LLM 进行语义审计,程序化脚本无法识别语义矛盾。
病 4:认知僵化病(Stale Content)📦
什么是它:
页面的内容停留在你两年前的认知水平,但你已经通过后来的阅读更新了相关判断——问题是 wiki 里的旧页面还不知道这件事。
为什么会发生:
5 在快速变化的领域,综合页面在来源之前就变得陈旧,而 Lint 检查并不能可靠地捕捉到这一点。
系统不能自动检测「你的认知已经进化了」这件事,它只能检测「这个页面很久没被更新了」这个代理指标。
危害: 表面上系统在正常工作,但给出的答案是过时的。更糟糕的是,高置信度的旧结论会让你停止搜索更新的证据。
治疗: 设置时效性阈值(超过 6 个月未更新的 high 级页面自动降级),并定期对核心领域触发重新 Ingest。
第二部分:Lint 是什么?Karpathy 的原始定义
在我们讲「怎么做」之前,先把 Karpathy 对 Lint 的原始定义讲清楚。
2 Lint 操作:定期请 LLM 对 wiki 进行健康检查。寻找:页面间的矛盾、被更新来源取代的过时声明、没有入链的孤儿页面、被提及但缺少独立页面的重要概念、缺失的交叉引用,以及可以通过网络搜索填补的数据缺口。 1 wiki 保持健康,是因为 LLM 做了没有任何人类愿意做的维护工作。
这句话,是理解 Lint 最重要的一把钥匙。
Lint 不是你的工作,是 LLM 的工作。
你不需要手动找矛盾,不需要手动检查每一个链接,不需要记得每个页面的更新日期。
你只需要定期触发 Lint,然后阅读报告,做人类最擅长的那件事:判断。
这是 LLM Wiki 整套系统里,分工最清晰的地方:
text
程序化脚本 → 找结构性问题(死链、孤儿、格式)
LLM → 找语义性问题(矛盾、过时、缺口)
你 → 看报告,做决定,不做执行
9 每周定期运行。审计矛盾、过时内容、孤儿页面和缺失的交叉引用。报告问题但不自动修复——由你决定如何处理。
第三部分:双层 Lint 架构——精确分工,高效运转
我在实践中把 Lint 分成了两层,每层负责不同类型的问题,用不同的工具来解决。
这个分层不是我发明的,而是从社区实践中观察到的最佳分工:
19 每次 Lint 运行检查损坏的链接、孤儿页面、格式错误的 frontmatter,以及(可选地,使用 LLM 进行)页面之间的矛盾。
Layer A:程序化 Lint(脚本,5 分钟跑完)
负责: 一切可以被精确定义、用逻辑判断的结构性问题。
特点: 秒级运行,零 API 成本,结果确定性 100%,可自动修复。
检查项目:
text
✅ 死链检查
扫描所有 [[wikilink]],验证目标文件是否存在
→ 输出:死链列表 + 正确路径建议✅ 孤儿页面检查
扫描 /wiki 下所有文件,找出没有任何入链的页面
→ 输出:孤儿列表 + 该页面曾被哪些文件"提到但未链接"
✅ YAML Frontmatter 完整性检查
验证每个页面是否包含:title, type, created, updated, sources, confidence
→ 输出:缺失字段的文件列表
✅ 索引漂移检查
比较 /wiki/index.md 的条目数量 vs /wiki 目录下的实际文件数量
→ 输出:index.md 中缺失的文件、index.md 中指向不存在文件的条目
✅ 时效性标记
扫描所有 confidence: high 的页面
检查 updated 日期是否超过阈值(默认 180 天)
→ 输出:「待复查」列表,不自动降级,等待人工确认
Layer B:语义 Lint(LLM 参与,30 分钟)
负责: 一切需要「理解意思」才能判断的语义性问题。
特点: 需要 LLM 参与,有 API 成本,结果是「建议」而非「判决」,需要人工决策。
检查项目:
text
🔍 跨页面矛盾检测
LLM 读取全量 wiki,寻找在不同页面里对同一问题存在明显冲突的声明
→ 输出:矛盾清单(A 页面的观点 vs B 页面的观点 + 建议处理方式)🔍 过时声明识别
对照最近新增的素材,检查现有页面中被新来源「超越」的旧结论
→ 输出:可能需要更新的声明列表
🔍 合并候选识别
寻找两个页面覆盖了高度重叠内容的情况
→ 输出:合并建议(哪两个页面可以合并,保留哪些内容)
🔍 知识缺口发现
寻找在多个页面中被频繁引用,但没有独立 wiki 页面的概念
→ 输出:「建议新建」列表(附上该概念在哪些页面里被提及)
🔍 置信度合规审查
检查是否有只有单一来源支撑但被标注为 high 的页面
→ 输出:置信度疑似虚高的页面列表
第四部分:Layer A 实现——你可以直接运行的脚本
以下是一个可以在 macOS / Linux 直接运行的 Python 脚本。
不需要任何外部依赖,不消耗 API,运行一次约 5-30 秒(取决于你的 wiki 体量)。
将以下内容保存为 /schema/scripts/lint_layer_a.py:
Python
#!/usr/bin/env python3
"""
阿木木数字大脑 · Layer A Lint 脚本
程序化健康检查:死链 / 孤儿 / Frontmatter / 索引漂移 / 时效性标记
运行方式:python3 schema/scripts/lint_layer_a.py
"""import os
import re
import yaml
from datetime import datetime, timedelta
from pathlib import Path
# ─────────────────────────────────────────────
# 配置区(根据你的目录结构调整)
# ─────────────────────────────────────────────
WIKI_DIR = "wiki"
INDEX_FILE = "wiki/index.md"
REPORT_DIR = "wiki/lint-reports"
STALE_THRESHOLD_DAYS = 180 # 超过多少天视为「时效待复查」
REQUIRED_FRONTMATTER = [ # 每个页面必须有的 YAML 字段
"title", "type", "created", "updated", "sources", "confidence"
]
# ─────────────────────────────────────────────
# 工具函数
# ─────────────────────────────────────────────
def get_all_wiki_files(wiki_dir):
"""获取 /wiki 下所有 .md 文件(排除系统文件)"""
system_files = {"index.md", "log.md", "glossary.md"}
files = {}
for path in Path(wiki_dir).rglob("*.md"):
rel = path.relative_to(wiki_dir)
stem = path.stem # 文件名不含扩展名
if path.name not in system_files:
files[stem] = path
return files
def extract_wikilinks(content):
"""从 Markdown 内容中提取所有 [[wikilink]]"""
return re.findall(r'\[\[([^\]|#]+?)(?:\|[^\]]*)?\]\]', content)
def parse_frontmatter(content):
"""解析 YAML frontmatter"""
if not content.startswith("---"):
return {}
end = content.find("---", 3)
if end == -1:
return {}
try:
return yaml.safe_load(content[3:end]) or {}
except yaml.YAMLError:
return {}
def date_from_str(date_val):
"""将 frontmatter 中的日期字段转为 datetime"""
if isinstance(date_val, datetime):
return date_val
if isinstance(date_val, str):
for fmt in ("%Y-%m-%d", "%Y/%m/%d"):
try:
return datetime.strptime(date_val, fmt)
except ValueError:
continue
return None
# ─────────────────────────────────────────────
# 检查模块
# ─────────────────────────────────────────────
def check_dead_links(wiki_files):
"""检查 1:死链检测"""
dead_links = []
for stem, path in wiki_files.items():
content = path.read_text(encoding="utf-8")
links = extract_wikilinks(content)
for link in links:
# 处理路径分隔符,取最后一段作为 stem
link_stem = Path(link).stem if "/" in link else link
if link_stem not in wiki_files:
dead_links.append({
"source_file": str(path),
"dead_link": link,
"link_stem": link_stem
})
return dead_links
def check_orphan_pages(wiki_files):
"""检查 2:孤儿页面检测"""
# 统计每个页面被链接的次数
inlink_count = {stem: 0 for stem in wiki_files}
for stem, path in wiki_files.items():
content = path.read_text(encoding="utf-8")
links = extract_wikilinks(content)
for link in links:
link_stem = Path(link).stem if "/" in link else link
if link_stem in inlink_count and link_stem != stem:
inlink_count[link_stem] += 1
orphans = [stem for stem, count in inlink_count.items() if count == 0]
return orphans
def check_frontmatter(wiki_files):
"""检查 3:Frontmatter 完整性"""
issues = []
for stem, path in wiki_files.items():
content = path.read_text(encoding="utf-8")
fm = parse_frontmatter(content)
missing = [field for field in REQUIRED_FRONTMATTER if field not in fm]
if missing:
issues.append({
"file": str(path),
"missing_fields": missing
})
return issues
def check_index_drift(wiki_files):
"""检查 4:索引漂移"""
if not os.path.exists(INDEX_FILE):
return {"error": "index.md 不存在"}
index_content = Path(INDEX_FILE).read_text(encoding="utf-8")
# 从 index.md 中提取所有 wikilink
index_links = set(extract_wikilinks(index_content))
wiki_stems = set(wiki_files.keys())
not_in_index = wiki_stems - index_links # 文件存在但 index 没有
ghost_in_index = index_links - wiki_stems # index 有但文件不存在
return {
"not_in_index": sorted(not_in_index),
"ghost_in_index": sorted(ghost_in_index)
}
def check_stale_pages(wiki_files):
"""检查 5:时效性标记(只标记,不自动降级)"""
stale = []
threshold = datetime.now() - timedelta(days=STALE_THRESHOLD_DAYS)
for stem, path in wiki_files.items():
content = path.read_text(encoding="utf-8")
fm = parse_frontmatter(content)
confidence = fm.get("confidence", "")
updated = fm.get("updated")
if confidence == "high" and updated:
updated_dt = date_from_str(updated)
if updated_dt and updated_dt < threshold:
stale.append({
"file": str(path),
"last_updated": str(updated),
"days_since_update": (datetime.now() - updated_dt).days
})
return stale
# ─────────────────────────────────────────────
# 报告生成
# ─────────────────────────────────────────────
def generate_report(dead_links, orphans, frontmatter_issues, index_drift, stale_pages):
today = datetime.now().strftime("%Y-%m-%d")
lines = [
f"# Lint Report · Layer A · {today}",
f"> 生成时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
"",
"---",
"",
"## 总览",
"",
f"| 检查项 | 发现问题数 | 状态 |",
f"|--------|-----------|------|",
f"| 死链 | {len(dead_links)} | {'⚠️ 需处理' if dead_links else '✅ 正常'} |",
f"| 孤儿页面 | {len(orphans)} | {'⚠️ 需处理' if orphans else '✅ 正常'} |",
f"| Frontmatter 缺失 | {len(frontmatter_issues)} | {'⚠️ 需处理' if frontmatter_issues else '✅ 正常'} |",
f"| 索引漂移 | {len(index_drift.get('not_in_index', [])) + len(index_drift.get('ghost_in_index', []))} | {'⚠️ 需处理' if (index_drift.get('not_in_index') or index_drift.get('ghost_in_index')) else '✅ 正常'} |",
f"| 时效待复查 | {len(stale_pages)} | {'📋 建议复查' if stale_pages else '✅ 正常'} |",
"",
"---",
"",
]
# 死链详情
lines.append("## 1. 死链(可自动修复)")
lines.append("")
if dead_links:
for item in dead_links:
lines.append(f"- **文件:** `{item['source_file']}`")
lines.append(f" - 死链:`[[{item['dead_link']}]]`")
lines.append(f" - 建议:检查是否有拼写相近的页面,或删除该链接")
lines.append("")
else:
lines.append("✅ 无死链,状态正常。")
lines.append("")
# 孤儿页面详情
lines.append("## 2. 孤儿页面(需人工判断)")
lines.append("")
if orphans:
lines.append("以下页面没有任何其他页面链接到它们。")
lines.append("处理选项:A) 在相关页面中添加链接 B) 归档此页面 C) 删除此页面")
lines.append("")
for stem in orphans:
lines.append(f"- `[[{stem}]]`")
lines.append("")
else:
lines.append("✅ 无孤儿页面,状态正常。")
lines.append("")
# Frontmatter 问题
lines.append("## 3. Frontmatter 缺失字段(可自动修复)")
lines.append("")
if frontmatter_issues:
for item in frontmatter_issues:
lines.append(f"- **文件:** `{item['file']}`")
lines.append(f" - 缺失字段:`{', '.join(item['missing_fields'])}`")
lines.append("")
else:
lines.append("✅ 所有页面 Frontmatter 完整。")
lines.append("")
# 索引漂移
lines.append("## 4. 索引漂移(建议更新 index.md)")
lines.append("")
not_in_index = index_drift.get("not_in_index", [])
ghost_in_index = index_drift.get("ghost_in_index", [])
if not_in_index:
lines.append("**文件存在但未在 index.md 中记录:**")
for stem in not_in_index:
lines.append(f"- `[[{stem}]]`")
lines.append("")
if ghost_in_index:
lines.append("**index.md 中有条目但对应文件不存在(幽灵条目):**")
for stem in ghost_in_index:
lines.append(f"- `[[{stem}]]`")
lines.append("")
if not not_in_index and not ghost_in_index:
lines.append("✅ 索引与文件系统完全同步。")
lines.append("")
# 时效待复查
lines.append("## 5. 时效待复查(建议重新 Ingest 相关素材)")
lines.append("")
if stale_pages:
lines.append(f"以下页面标注为 `confidence: high`,但超过 {STALE_THRESHOLD_DAYS} 天未更新:")
lines.append("")
for item in stale_pages:
lines.append(f"- **文件:** `{item['file']}`")
lines.append(f" - 最后更新:`{item['last_updated']}`({item['days_since_update']} 天前)")
lines.append(f" - 建议:投入该领域的新素材,触发重新评估")
lines.append("")
else:
lines.append("✅ 所有 high 级页面均在时效范围内。")
lines.append("")
lines.append("---")
lines.append("")
lines.append("**下一步:**")
lines.append("1. 将此报告发给 Claude Code,请它修复标注为「可自动修复」的问题")
lines.append("2. 人工判断「孤儿页面」和「时效待复查」的处理方式")
lines.append("3. 将需要语义审计的问题,传入 Layer B Lint Prompt")
lines.append("")
lines.append(f"*报告生成于 {datetime.now().strftime('%Y-%m-%d %H:%M')}*")
return "\n".join(lines)
# ─────────────────────────────────────────────
# 主程序
# ─────────────────────────────────────────────
def main():
print("🔍 阿木木数字大脑 · Layer A Lint 正在运行...")
print(f" 扫描目录:{WIKI_DIR}")
print("")
wiki_files = get_all_wiki_files(WIKI_DIR)
print(f" 发现 wiki 页面:{len(wiki_files)} 个")
print("")
print(" [1/5] 检查死链...")
dead_links = check_dead_links(wiki_files)
print(" [2/5] 检查孤儿页面...")
orphans = check_orphan_pages(wiki_files)
print(" [3/5] 检查 Frontmatter...")
frontmatter_issues = check_frontmatter(wiki_files)
print(" [4/5] 检查索引漂移...")
index_drift = check_index_drift(wiki_files)
print(" [5/5] 标记时效待复查...")
stale_pages = check_stale_pages(wiki_files)
print("")
print(" 生成报告...")
# 生成报告
report = generate_report(dead_links, orphans, frontmatter_issues, index_drift, stale_pages)
# 保存报告
os.makedirs(REPORT_DIR, exist_ok=True)
today = datetime.now().strftime("%Y-%m-%d")
report_path = f"{REPORT_DIR}/lint-layer-a-{today}.md"
with open(report_path, "w", encoding="utf-8") as f:
f.write(report)
# 控制台摘要
total_issues = len(dead_links) + len(orphans) + len(frontmatter_issues)
total_issues += len(index_drift.get("not_in_index", [])) + len(index_drift.get("ghost_in_index", []))
print(f"✅ Layer A Lint 完成!")
print(f" 发现问题:{total_issues} 个(另有 {len(stale_pages)} 个时效待复查)")
print(f" 报告已保存至:{report_path}")
print("")
print(" 下一步:")
print(f" 1. 用 Obsidian 打开 {report_path} 查看详情")
print(" 2. 将报告发给 Claude Code,执行自动修复")
print(" 3. 运行 Layer B 语义 Lint 进行深度检查")
if __name__ == "__main__":
main()
第五部分:Layer B 实现——语义 Lint 的完整 Prompt
Layer A 搞定了所有「结构性问题」。
但真正影响知识质量的,是那些只有「理解意思」才能发现的问题——矛盾、过时、缺口。
这些,需要 LLM 来做。
把以下 Prompt 发给 Claude Code,触发一次完整的语义审计:
Layer B 语义 Lint 主 Prompt
text
你正在执行一次 wiki 语义健康检查(Layer B Lint)。目标目录:/wiki
Layer A 程序化 Lint 已完成,报告位于:/wiki/lint-reports/lint-layer-a-[今日日期].md
请按以下顺序执行语义审计:
──────────────────────────────────────
STEP 1:读取全局索引
──────────────────────────────────────
读取 /wiki/index.md,获取所有页面的完整列表。
这是你本次审计的工作清单。
──────────────────────────────────────
STEP 2:跨页面矛盾扫描
──────────────────────────────────────
从 index.md 中,选取置信度标注为 contested 或近期有「⚡矛盾标记」的页面。
逐一深入阅读,寻找以下类型的矛盾:
类型 A:直接矛盾
A 页面说「X 是 Y」,B 页面说「X 不是 Y」
类型 B:条件性矛盾
A 页面的结论在条件 P 下成立,B 页面的结论在条件 Q 下成立,
但两个页面都没有标注各自的适用条件
类型 C:时间性矛盾
A 页面引用了旧来源的旧结论,B 页面引用了新来源的新结论,
两者并列但未标注时间上的演进关系
输出格式(每个矛盾一条):
【矛盾 N | 类型:A/B/C】
页面 1:[[page-name]] → 立场摘要(不超过 30 字)
页面 2:[[page-name]] → 立场摘要(不超过 30 字)
矛盾性质:[解释为什么这是矛盾]
建议处理方式:
方案 1:[如果来源质量 A > B,保留 A 的结论,将 B 的结论移入「历史观点」节]
方案 2:[如果两者均有道理,创建综合分析页,显式标注适用条件]
方案 3:[如果信息不足判断,两个页面均标注「contested」,等待更多来源]
等待用户决策:是
──────────────────────────────────────
STEP 3:过时声明识别
──────────────────────────────────────
检查 /raw 目录中最近 30 天新摄入的素材(通过 /wiki/log.md 确认日期)。
对每篇新素材,检查它是否「超越」了某个现有 wiki 页面的核心结论:
判断标准:
- 新素材明确提出对旧观点的更新或反驳
- 新素材提供的数据/案例比现有页面的来源更新、更有说服力
- 现有页面引用的工具/方法已有新版本,旧版本的结论不再完全适用
输出格式(每个过时声明一条):
【过时声明 N】
现有页面:[[page-name]],相关段落:「[30 字以内摘要]」
超越来源:raw/[文件路径],相关内容:「[30 字以内摘要]」
建议操作:更新该页面的对应段落,追加新来源,调整置信度(如适用)
──────────────────────────────────────
STEP 4:合并候选识别
──────────────────────────────────────
扫描 index.md,寻找可能覆盖高度重叠主题的页面对。
判断标准:
- 两个页面的 related 字段互相引用
- 两个页面的核心定义有超过 50% 的概念重叠
- 其中一个页面的内容可能是另一个页面的子集
输出格式(每个合并候选一条):
【合并候选 N】
页面 A:[[page-name-a]](一行摘要)
页面 B:[[page-name-b]](一行摘要)
重叠分析:[解释重叠的具体内容]
合并建议:
- 保留页面:[[page-name-a 或 b]](建议保留哪个)
- 重定向处理:被合并的页面改为重定向文件,保留 slug
- 需要保留的独特内容:[列出被合并页面中不重叠的部分]
等待用户决策:是
──────────────────────────────────────
STEP 5:知识缺口扫描
──────────────────────────────────────
扫描所有 wiki 页面,寻找「被多次引用但没有独立页面的概念」:
具体操作:
1. 统计所有 [[wikilink]] 中,指向不存在页面的链接频率
2. 频率 ≥ 3 次的「缺失概念」,列入「建议新建」清单
同时检查:
3. 在页面正文中被多次提到(不是以 [[link]] 形式)但没有独立页面的重要术语
输出格式:
【知识缺口 N】
缺失概念:[概念名称]
被引用次数:N 次
来源页面:[[page-a]], [[page-b]], ...
建议操作:新建 /wiki/concepts/[kebab-case].md
核心内容建议:[该概念应包含哪些核心内容]
──────────────────────────────────────
STEP 6:置信度合规审查
──────────────────────────────────────
抽查 20 个标注为 confidence: high 的页面(优先选最近创建的)。
验证:每个 high 级页面是否真的有 3 个以上高质量、互相独立的来源支持。
发现不合规的情况:
- 只有单一来源但标注为 high → 建议降为 medium
- 来源都指向同一个人/机构 → 建议降为 medium(来源不独立)
- 来源超过 12 个月旧 → 建议降为 medium,标注「待更新来源」
──────────────────────────────────────
STEP 7:生成 Layer B 报告
──────────────────────────────────────
将上述所有发现整合为一份 Markdown 报告,保存至:
/wiki/lint-reports/lint-layer-b-[今日日期].md
报告结构:
## 执行摘要
(总共发现 N 个矛盾、M 条过时声明、K 个合并候选、J 个知识缺口)
## 需要立即处理(高优先级)
(直接影响答案质量的矛盾 + 置信度严重不合规)
## 建议处理(中优先级)
(过时声明 + 合并候选)
## 等待更多素材(低优先级)
(知识缺口 + 单来源 high 页面)
## 下次 Ingest 建议
(基于知识缺口,推荐搜索和摄入哪些类型的素材)
第六部分:Lint 报告出来了——然后呢?
很多人走到这一步,拿到了报告,然后不知道该怎么办。
Lint 报告,不是让你自己去修复所有问题的。
记住你的角色:你是策展人和决策者,不是执行者。
这是收到 Lint 报告后的标准处理流程:
第一步:阅读执行摘要(5 分钟)
看总数,不看细节。
问自己两个问题:
有没有「高优先级」的矛盾,可能正在影响我的日常使用? 有没有「知识缺口」,说明我在某个重要领域的素材严重不足?
第二步:处理自动修复项(10 分钟)
把 Layer A 报告发给 Claude Code,说这一句话:
"请读取这份 Lint 报告,对所有标注为「可自动修复」的问题执行修复,包括:修复死链(如有明确对应页面)、补全缺失的 Frontmatter 字段、更新 index.md 的漂移条目。修复完成后,告诉我做了哪些修改。"
你不需要看过程,只看结果报告。
第三步:处理矛盾(需要你的判断,每条 3-5 分钟)
对每一个矛盾,Layer B 报告已经给了你三个处理方案。
你的工作是选择哪个方案,然后告诉 Claude Code 去执行:
"关于矛盾 2(rag vs llm-wiki 关于向量搜索的矛盾), 请采用方案 2:创建综合分析页 rag-vs-llm-wiki-on-vector-search, 在两个原始页面添加「⚡矛盾已解决,参见综合分析页」的标注。"
Lint 操作明确检查页面之间的矛盾、被更新来源取代的过时声明,以及缺失的连接。这是最大的缺口——当新信息到来时,应该检查它是否与现有内容矛盾并标记冲突,而不是静默覆盖。
静默覆盖是质量最大的敌人。 矛盾被显式标记,比矛盾被悄悄掩盖,要好一千倍。
第四步:处理知识缺口(每月一次,作为 Ingest 选题来源)
Layer B 报告里的「知识缺口」,是你下一个月 Ingest 工作的选题库。
它告诉你:你的知识网络里有哪些节点是空的,但被其他节点反复引用着。
把知识缺口清单复制进你的 seeds 文件夹,作为下个月阅读计划的输入。
这是整个系统最优雅的闭环之一:
Lint 发现缺口 → 缺口变成阅读选题 → 阅读产生素材 → 素材 Ingest 填补缺口 → wiki 更完整 → 下次 Lint 发现更深的缺口 → 循环
LLM 善于建议新的研究问题和新的素材来源。这让 wiki 随着成长保持健康。
第五步:不处理的,写进决策日志(2 分钟)
对于那些你看了、但决定暂时不处理的问题——
在 /wiki/log.md 里追加一条记录:
text
## 2026-05-23 | Lint 决策日志
矛盾 3([[compilation]] vs [[build-systems]]):暂缓处理,等待摄入更多软件工程领域素材后再做综合判断。
知识缺口([[system-prompt-learning]]):已加入下月 Ingest 优先清单。
知识溯源日志——每次 Ingest 的时间序列记录——记录了什么内容进入知识库、什么时间、来自哪个来源、影响了哪些页面。可以用 grep 快速搜索。简单但对调试和复查极有价值。
这条记录很重要:它让你三个月后再看 log.md 时,知道为什么这个矛盾当时没有被解决,而不是认为它被遗漏了。
第七部分:你的数字大脑季度体检清单
把以下清单存进你的系统。这是每个周期应该做的最小必要动作:
text
📅 每月体检(约 45 分钟)【第一步:运行 Layer A 脚本(5 分钟)】
□ 运行 python3 schema/scripts/lint_layer_a.py
□ 打开报告,浏览执行摘要
□ 将报告发给 Claude Code,执行自动修复
【第二步:处理明显错误(10 分钟)】
□ 修复所有死链
□ 为孤儿页面决定:补链接 / 归档 / 删除
□ 更新 index.md 的漂移条目
【第三步:记录决策(5 分钟)】
□ 将暂缓处理的问题记录进 /wiki/log.md
□ 将新发现的知识缺口加入下月 Ingest 选题
---
📅 每季审计(约 2.5 小时)
【第一步:运行 Layer A + Layer B(30 分钟运行 + 等待)】
□ 运行 Layer A 脚本
□ 发送 Layer B 语义 Lint Prompt,等待报告
【第二步:处理矛盾(60 分钟)】
□ 逐一阅读所有矛盾,选择处理方案
□ 发给 Claude Code 执行
□ 验证处理结果
【第三步:处理过时声明(30 分钟)】
□ 对每条过时声明,确认是否需要更新
□ 对「时效待复查」的页面,决定是否重新 Ingest 相关素材
【第四步:知识缺口转化(15 分钟)】
□ 将 Top 5 知识缺口转化为下季度的 Ingest 优先清单
□ 每个缺口对应一篇「种子文章」,存入 /raw/seeds/
【第五步:Schema 回顾(15 分钟)】
□ 回顾本季度最常触发矛盾的领域——Schema 的矛盾检测规则是否需要更新?
□ 回顾孤儿问题最多的页面类型——命名规范是否需要调整?
□ 如有改进,更新 CLAUDE.md 并追加到 LEARNED.md
---
📅 年度大审(约 半天)
□ 评估 CLAUDE.md 整体是否需要架构性升级
□ 统计全年 Lint 发现的问题类型分布——找规律,优化 Schema
□ 检查知识网络的「枢纽节点」——这些节点代表你理解最深的领域,是否和你的核心价值主张一致?
□ 清理「永久孤儿」:超过 1 年没有被链接到的页面,考虑归档
□ 备份整个 /wiki 目录到外部存储
第八部分:一个更大的视角——Lint 不是维护,是进化
我想在结束之前,说一件超越「操作手册」范畴的事。
很多人听到「体检」「维护」「审计」这些词,就觉得这是负担——是系统给你增加的额外工作量。
但我越用这套系统,越觉得:
Lint 不是负担,是系统给你的礼物。
Karpathy 的 Gist 揭示了我的生产级系统里一些我曾经视而不见的缺口——因为我是一点一点增量搭建起来的,用我的学习循环作为指引。我的系统没有全局的 Lint 操作,没有孤儿检测,没有死链扫描,没有过时内容识别。
这是真正走在前面的实践者的诚实反馈:没有 Lint 的知识系统,是带着蒙眼布在成长。
你不知道它的盲点在哪里,不知道它的矛盾在哪里,不知道哪些「确信」已经过期了。
而当你第一次拿到 Layer B 报告,看到系统找到了一个你自己从未意识到的矛盾——
那个矛盾的存在,说明了一件事:
你的知识系统,已经聪明到能够发现你自己的认知盲点了。
这才是「数字大脑」真正应有的样子。
不是一个更好的收藏夹,不是一个更整齐的笔记本,而是一个能够挑战你、更新你、帮你看到你自己看不到的东西的外部认知系统。
Lint,是这个系统保持这种能力的机制。
LLM Wiki 解决了维护问题——wiki 能保持维护,是因为维护成本接近于零。LLM 在每次 Ingest 时自动创建和更新交叉引用。人类专注于真正重要的事——决定读什么和提什么问题。
你专注于好奇心和判断力。系统负责一切的记录、连接、和健康维护。
这,才是知识工作者在 AI 时代真正的分工。
尾声:给你的系统安排第一次体检
读完这篇文章,我希望你做一件事:
今天,给你的系统安排第一次体检。
不需要等到系统很大。哪怕你现在只有 20 个 wiki 页面,跑一次 Layer A 脚本,都可能发现你没注意到的问题。
而「第一次发现问题」,是整个 Lint 流程里最有价值的时刻——
因为它会改变你看待自己知识系统的方式:
从「我有一堆笔记」,到「我有一个需要持续维护、持续进化、但不需要我亲自维护的知识引擎」。
只需要四步:
text
第一步:把本文第四部分的脚本保存到 schema/scripts/lint_layer_a.py
第二步:在你的 Vault 根目录打开终端,运行:
python3 schema/scripts/lint_layer_a.py
第三步:打开报告,看看发现了什么
第四步:把报告发给 Claude Code,说:
"请读取这份 Lint 报告,修复所有「可自动修复」的问题"
就这四步。
然后,你的系统会比今天更健康一点。
下次,再健康一点。
一次一次的 Lint,就是你的知识系统,不断进化的过程。
下一篇预告:
系统建好了,能运转了,也能自我维护了。
下一篇,我要讲的是整个系列里最前沿的内容——
LEARNED.md:让你的数字大脑自己「长脑子」。
当你的系统开始把自己踩过的坑记录下来, 开始自己更新自己的编译规则, 它就从一个「工具」进化成了一个「正在成长的认知伙伴」。
这,是 Karpathy 指向但还没有完全解决的那个方向。 而我们,会尝试走得比他更远一点。
关注一只阿木木,别让这个系列在你的收藏夹里睡觉。去做,才是真的学。🌊
本文参考资料:Andrej Karpathy,「LLM Wiki」,GitHub Gist,2026 年 4 月;社区实践参考:karpathy-llm-wiki(Astro-Han)、obsidian-llm-wiki(green-dalii)、llm-knowledge-bases(rvk7895),2026 年 4-5 月。
扫码加入行动营👇获取更多Obsidian + AI数字大脑实践
关注一只阿木木,别让这个系列在你的收藏夹里睡觉。去做,才是真的学。🌊