lint · diff

git:20261008.0db1eb2 to git:20261009.77312f4

3 added, 0 removed. Audit A to A.

---
name: lint
description: 知识库全局健康度检查。扫描 wiki/ 目录,按 AGENTS.md 规范执行九项检查:索引一致性、死链与孤儿页面、文中提及但缺少页面的概念、过时论断、核心问题覆盖、空白领域建议、概念分层一致性、Canvas 同步、Tag 受控词表合规。支持 `/lint`、`/scan`、`/health` 触发。当用户说「检查知识库」「跑一下 lint」「wiki 健康状况怎么样」「帮我检查一下 wiki」时触发。默认仅读,修复需用户确认。
user-invocable: true
---
# lint 技能:知识图谱健康巡检
## 前置:初始化
若 `.dsh/wiki.config.json` 或根目录的 `index.md` 不存在,说明骨架尚未生成,先跑一次 `python .dsh/scripts/setup_wiki.py`;没有 wiki 内容时巡检会全部落空,此时直接告知用户即可。
## 路径以配置为准
`.dsh/wiki.config.json` 是本仓**唯一配置源**,目录名、文件名、画布名都从它读:
```bash
python .dsh/scripts/wiki_env.py --paths # 常用目录一览(wiki / raw / inbox / index / log / tags_doc / vocab)
python .dsh/scripts/wiki_env.py --json # 完整配置 + 解析结果
```
> 下文出现的 `wiki/`、`index.md`、`log.md`、`TAGS.md`、`.dsh/tag-vocab.json` 等**均为默认值**;若配置改过,以配置为准,拿不准先跑上面的自省命令。
## 核心目标
将软件工程中的"静态分析"引入知识管理。定期运行此 skill,按照 AGENTS.md 定义的九项检查,找出知识库在长期演进中积累的问题:死链、孤岛、未同步索引、缺失页面、过时论断、研究空白,以及概念分层、Canvas 地图与 tag 词表的漂移。
## 触发条件
- 用户输入 `/lint`
- 用户询问"知识库健康状况如何"、"帮我检查一下 wiki"、"跑一下 lint"
- 用户要求"检查知识库状态"
## 知识库范围
**扫描范围**(按检查项不同):
| 范围 | 用途 |
|---|---|
| `wiki/` 五个子目录 | 检查 1 的"未注册页面"、检查 2 的孤儿判定、检查 4/5 的页面级统计 |
| 根目录 `*.md`(`index.md`、`log.md`、`AGENTS.md`) | 检查 2 的**死链**必须覆盖它们——只扫 `wiki/` 会漏掉这些文件里的链接 |
| 全库 basename 索引(含 wiki 之外的笔记目录,默认 `我的研究/`,见配置 `dirs.my_research`;以及 `.canvas`/`.pdf` 附件) | 判定链接目标是否存在(Obsidian 按 basename 解析,见检查 1) |
`raw/` 不参与 lint——它是不可变的原始资料层。
## 巡检流水线(九项检查,与 AGENTS.md 对齐)
---
### 检查 1:索引一致性
**目标**:确保 `index.md` 中注册的页面与实际文件一一对应。
**方法**:
1. 读取 `index.md`,提取所有 `[[页面名称]]` 格式的注册条目,按分类(Papers / Concepts / Connections / Questions / Syntheses)归类
2. 用 `glob` 扫描 `wiki/` 下所有 `.md` 文件(`index.md` 和 `log.md` 位于根目录,不会被 glob 匹配到)
3. 交叉比对,输出两类问题:
| 问题 | 含义 |
|---|---|
| **幽灵条目** | index.md 中注册了 `[[X]]`,但**全库任何位置**都没有 `X.md` |
| **未注册页面** | `wiki/` 下的 `X.md` 存在,但 index.md 中没有任何分类下列出它 |
⚠️ **判定链接是否存在时,必须按 Obsidian 的规则:basename 全局唯一**。`[[X]]` 指向的是**任意目录**下名为 `X.md` 的文件,不限于 `wiki/`。因此:
- 存在于 wiki 之外的笔记目录(如默认的 `我的研究/`)里的笔记是**合法的链接目标**,不得报为幽灵条目
- 实例:`[[某篇调研]]` 指向 `我的研究/调研/某篇调研.md`,是合法条目
- 链接目标也可能**不是 `.md`**:Obsidian 维基链接能指向 `.canvas`、`.pdf` 等附件
- 实例:`[[某张画布.canvas]]` 指向 `我的研究/某张画布.canvas`,合法
- 只扫 `wiki/` 会把上述两类全部误报。检查前先建立**全库 basename 索引**(含根目录、wiki 之外的笔记目录、附件)
**必须排除的三类"假死链"**,否则报告会被噪音淹没:
1. **文档占位符**:`AGENTS.md` 的模板示例里的 `[[页面名称]]`、`[[EntityName]]`、`[[ConceptName]]`、`[[摘要-source-slug]]` 等是**格式说明**,不是真实链接。行内代码 `` `[[概念名称]]` `` 内的同理。
2. **`log.md` 的历史引用**:日志是 append-only 的历史记录,其中的 `[[已删页面]]`、`[[旧版调研]]` 反映的是当时的真实操作。**不得据此修改日志**,只作为"历史上存在过但已删除"的信息记录。
3. **路径型引用**:`[[raw/papers/xxx.pdf]]` 是文件引用(见检查 2 说明),不参与存在性判定。
**同时校验 frontmatter 与必需小节**(`index.md` 对不上只是表象,字段缺失才是根因):
1. 每个页面的 frontmatter 必须含 `title`、`type`、`created`、`updated`、`tags`;`sources` 除纯综述页外应存在
- `tags` 的**取值**是否合法由**检查 9(Tag 规范)**负责,此处只看字段是否存在
2. `type` 必须与所在目录匹配:`papers/`→`paper`、`concepts/`→`concept`、`connections/`→`connection`、`questions/`→`question`、`syntheses/`→`synthesis`(目录↔type 的对应表以配置的 `wiki_types` 段为准)
3. **每个页面必须有 `## 关联连接` 小节**——这是 AGENTS.md 的硬性要求。判定时注意三点:
- 用**包含匹配**而非精确匹配:`## 五、关联连接` 这类带编号的标题算合规,`### 关联连接`(级别写成三级)算**违规但易修**,单独列为一类
- `## 涉及论文` / `## 相关论文` / `## 涉及概念` 之类的分节**不能替代**它(有则可以共存,缺了它就判不通过)
- 小节应位于页面**末尾**;若出现在开头,Obsidian 大纲与阅读顺序都不对,也列入待修
4. 顺带检查文件编码:frontmatter 必须能被解析,即文件**不得带 UTF-8 BOM**(BOM 会让首行变成 `\uFEFF---`,整个页面被判为"无 frontmatter")
> **经验**:`## 关联连接` 是最常被漏掉的必需小节,尤其 connection / question / synthesis 三类页面;`updated` / `sources` 字段则在 question 页面上最容易被漏掉。这两类问题如果检查项不覆盖,就会长期无人发现——所以它们必须进巡检清单。
---
### 检查 2:死链与孤儿页面
**目标**:确保双向链接网络的健康。
**方法**:
1. 用 `grep` 扫描所有 `.md` 文件,提取所有 `[[...]]` 格式的双链
⚠️ 本宿主(DSH)的检索工具是 `grep`,不是 `search_content` / `search_files`。用错名字会让检查 2、3 直接失效。
2. 过滤掉指向 raw/ 的链接、指向自身的链接
3. 对每个目标,检查对应 `.md` 文件是否存在
4. **死链**:链接指向不存在的页面
5. **孤儿页面**要分两级报告,二者修复动作不同:
- **真孤岛**:既没有任何页面引用,也没有在 index.md 注册 → 严重,必须处理
- **仅索引可达**:有 index.md 注册,但**零个页面用 `[[...]]` 引用它** → 轻度,它实际上没进入知识网络
6. **命名不一致单列**:若某死链目标是不存在的英文 slug,而 `wiki/` 下存在语义对应的中文文件(反之亦然),归入「命名不一致」而非「缺失页面」
7. **扫描范围必须包含根目录文件**:`index.md`、`log.md`、`AGENTS.md` 也含有 `[[...]]` 双链。只扫 `wiki/` 会漏掉这些链接里的死链——它们是根目录文件,**不算 wiki 页面**,因此判断存在性时要同时看根目录和 `wiki/` 各子目录。
**⚠️ 解析双链的三个坑(不处理会产生大量假阳性)**:
1. **表格内的别名链接用转义竖线**:Obsidian 在表格里写 `[[目标\|别名]]`。提取目标时必须在**第一个 `|` 或 `\|` 处截断**,否则目标名会带上 `\|别名`,被误报成死链。
2. **标题锚点**:`[[页面#小节]]` 的目标是 `页面`,`#` 之后要丢掉。
3. **英文 slug 与中文文件名的命名漂移**:若链接目标是不存在的英文 slug,**先在 `wiki/` 里搜同义的中文文件名**(例如 `[[sparse-coding-attention]]` 对应 `稀疏编码与注意力.md`)。这类应归类为「**命名不一致**」,而不是「缺失页面」——两者的修复动作完全不同(前者是改链接或建别名,后者才需要新建页面)。
4. 指向 `raw/` 或含 `/` 的路径引用不算死链——它们是文件引用,不是 wiki 页面引用。
---
### 检查 3:文中提及但缺少独立页面的概念
**目标**:发现那些在正文中被频繁讨论、但尚未拥有独立 concept 页面的概念。
**方法**:
1. 收集所有死链中有意义的非拼写错误目标(如 `[[反向传播]]` 大概率是缺页面,而 `[[asdf]]` 是笔误)
2. 用 `grep` 扫描所有页面的 `## 关联连接` 区域,提取以纯文本形式出现但未用 `[[]]` 包裹的概念名——这些是"应该链接但没链接"的信号
3. 合并去重,输出"建议创建的概念页面"清单,按被提及次数排序
**输出示例**:
> ⚠️ 以下概念在 wiki 中被多次引用但缺少独立页面:
> - "稀疏编码" — 在 3 个页面的关联连接中被提及
> - "自由能原理" — 在 2 个页面中出现死链
---
### 检查 4:过时论断检测
**目标**:找出可能被新资料推翻的陈旧说法。
**方法**:
1. 扫描所有 paper 页面的 frontmatter,提取 `year` 字段
2. 扫描所有 concept 页面的 frontmatter,提取 `updated` 字段和 `sources` 字段
3. 对每个 concept 页面,检查其引用的 sources 中最新 paper 的年份
4. 标记以下情况:
- concept 页面的 `updated` 日期距今超过 6 个月,且其引用的所有 paper 均为 2 年以前
- 同一概念存在新旧两篇 paper,旧 paper 的结论与新 paper 冲突,但 concept 页面未记录此冲突
**局限性说明**:此项检查无法完全自动化判断"论断是否真的过时"——它只能标记**可能**需要复核的页面。最终判断由用户做出。
---
### 检查 5:核心研究问题覆盖分析
**目标**:找出覆盖薄弱的核心研究问题。
**方法**:
1. 搜索 `wiki/` 下是否有定义核心研究问题的文件(如 `wiki/questions/` 下的页面,或某个专门的配置文件)
2. 扫描所有 wiki 页面的 frontmatter `questions` 字段,统计每个问题编号被多少页面标注
3. 输出覆盖矩阵:
| 核心问题 | 标注页面数 | 状态 |
|---|---|---|
| 1: [问题描述] | 12 | ✅ 充分 |
| 2: [问题描述] | 3 | ⚠️ 薄弱 |
| 3: [问题描述] | 0 | ❌ 空白 |
如果 `wiki/` 中尚未定义核心研究问题,报告此项为"跳过"并建议用户先定义。
---
### 检查 6:空白领域建议
**目标**:基于以上所有检查的综合结论,给出可操作的建议。
**方法**:
1. 汇总检查 3(缺失概念)、检查 4(可能过时)、检查 5(覆盖薄弱问题)
2. 针对每个薄弱领域,建议具体的论文或主题方向
3. 检查收件箱(默认 `raw/research/`)中是否有尚未摄入的资料恰好能填补这些空白 → 一并提醒
---
### 检查 7:概念分层一致性(母概念 / 子概念)
**目标**:保证「母页 + 子页」两级结构在 **tag、正文链接、索引**三处一致——分层只有在三处同时成立时才会在 Obsidian 关系图谱中可见(核心图谱的边只来自正文 `[[链接]]`,frontmatter 纯文本不进图谱)。
**方法**(只读扫描):
1. 取 `tags` 含 `母概念` 的页面为母页集合、含 `子概念` 的为子页集合
2. 对每个母页:
- 必须存在 `## 子概念` 段,否则报「有 tag 无子概念段」
- `## 子概念` 段中,每条 bullet **`—` 之前的链接**才是子页声明(描述文字里的链接不计入,它们属 `## 关联连接` 范畴);同一条 bullet 可含多个声明(以 `、` 分隔)。每个声明链接的目标必须存在,且其文首定位行应含 `> **母概念**:…[[本页]]…`,否则报「单向声明」。**注意**:不要整行抓取所有链接——描述文字中的链接会造成假阳性
- 不得把自己列为子页(自环)
3. 对每个子页:文首定位行中的每个母页必须存在,且该母页的 `## 子概念` 段必须列出本页,否则报「孤儿声明」
4. **环检测**:若 A 的 `## 子概念` 含 B 且 B 的含 A → 报环(**多父 DAG 本身不是问题,环才是**)
5. 索引一致性:子页应作为缩进条目出现在 `index.md` 的某个母页下(或它本身也是母页)
**输出**:黄灯项「分层不一致 N 个」,逐条给出页面与缺失项;总览中追加「母页 N / 子页 N」。
---
### 检查 8:Canvas 同步(族地图 / 总图)
**目标**:保证「族结构」与画布一致——族变了却没重生成画布时,画布会展示陈旧结构(错误分组、缺失子页、失效链接)。
**方法**:
1. 运行 `python .dsh/scripts/gen_canvas.py --check`(只需标准库,用 harness 自带解释器),**退出码 0 = 全部新鲜**
2. 退出码非 0 时报告清单:`MISSING`(画布缺失)与 `STALE`(结构指纹失配,附旧→新指纹),并提示运行同脚本(不带 `--check`)重新生成
3. 附加只读校验:画布 JSON 中**不得出现 `"type": "file"` 节点**(必须全部为 `text` 卡片 + `[[双链]]`);每个 `tags` 含 `母概念` 的页面都应有对应的族图画布(文件名见配置 `canvas.family_prefix`)
4. 触发条件提醒:新增/删除母页或子页、改动母页 `## 子概念` 段、改动子页文首定位行(多父)、母页改名 —— 任何一项都要求重新生成
**输出**:红灯项「画布过期 N 张 / 缺失 N 张」(若仓库尚无画布则记为"跳过")。
---
### 检查 9:Tag 规范(受控词表)
**目标**:保证 `tags` 字段始终落在 `.dsh/tag-vocab.json`(`canonical` 段)定义的封闭词表内——这是防止 tag 膨胀的唯一机制。**经验**:不设封闭词表时,tag 数量会随页数线性膨胀,每个页面都造几个新词,最终彻底失去检索价值。
**方法**(优先用脚本,不要靠肉眼):
1. 运行 `python .dsh/scripts/tag_audit.py`(只需标准库,用 harness 自带解释器),**退出码 0 = 全部合规**。
2. 退出码非 0 时,逐条报告:
- **未登记 tag**(红灯):出现在 frontmatter 但不在 `canonical` 中。可用
`tag_audit.py --unregistered` 只看这一类。修复方式见下。
- **硬性违规**(红灯):单页主题 tag > 8 个,或 0 个主题 tag。
3. 只读补充校验(脚本已覆盖大部分,异常时人工复核):
- 角色 tag `母概念` / `子概念` 是否仍位于 `tags` **首位**(`gen_canvas.py` 依赖)
- 是否仍存在 YAML 块序列写法的 `tags:`(应统一为单行内联)
- 分面软上限:domain 2 / task 4 / modality 4 / method 6 / challenge 4 / data 2 / meta 2
- 推荐区间 3–5 个/页(仅提示,不报错)
4. **修复路径**(二选一,不要直接改页面 tag):
- 该 tag 其实是已有词条的近义/大小写/单复数变体 → 在 `.dsh/tag-vocab.json` 的 `map` 里
登记「来源词 → 规范词」,然后 `tag_apply.py`(先预演,再 `--apply`)。
- 该 tag 确有独立检索价值(**≥3 个页面**用得上,且能归入某个分面)→ 在 `canonical`
增加词条 + `map` 登记来源(`TAGS.md` 只写规则、不列词条,无需刷新)。
+ **⚡ 冷启动例外**:若 `canonical` 为空(出厂状态)或该分面为空,**免于 3 页门槛**——
+ 首次摄入本来就不可能攒够页面。此时应把这些 tag 报成「**待登记**」而非「违规」,
+ 并提示登记进词表即可,**不要建议删除页面上的 tag**。
- 两者都不成立 → 删掉这个 tag(信息写进正文即可)。
5. 收尾:修改词表后按 AGENTS.md「Tag 规范 → 改动流程」五步执行
(改 `canonical` / `map` → `tag_vocab.py --check` → `--emit-doc` → `tag_apply.py --apply` → `tag_audit.py`),
并确认 `tag_vocab.py --check` 与 `tag_audit.py` 均返回 0。
**输出**:红灯项「未登记 tag N 个 / 硬违规 N 个」;黄灯项「超软上限 N 处」;
总览中追加「规范 tag 使用数 N / 词表容量(从 `.dsh/tag-vocab.json` 统计)」与 tag 数分布。
---
## 报告输出规范
扫描完成后,输出结构化报告:
```markdown
## 🩺 知识库健康体检报告 — YYYY-MM-DD
### 📊 总览
- 页面总数:N(papers: N, concepts: N, connections: N, questions: N, syntheses: N)
- 双链总数:N
- 整体评分:[健康 / 需要注意 / 需要修复]
### ❌ 红灯(需立即处理)
- **死链 N 个**:[来源页面] → [[不存在的目标]]
- **幽灵条目 N 个**:index.md 注册了但文件不存在
- **真孤岛 N 个**:无引用且未注册的页面
### ⚠️ 黄灯(建议处理)
- **缺 `## 关联连接` 的页面 N 个**:[列表] — 违反 AGENTS.md 硬性要求
- **frontmatter 字段不全 N 个**:[页面] 缺 [字段]
- **仅索引可达 N 个**:[列表] — 已注册但无任何页面引用
- **未注册页面 N 个**:[列表]
- **命名不一致 N 个**:[英文 slug] ↔ [中文文件名]
- **未解决的知识冲突 N 个**:[页面名称]
### 🔵 提示(优化建议)
- **建议创建的概念页面 N 个**:[列表] — 在多个页面中被提及但无独立页面
- **可能需要复核的页面 N 个**:[列表] — 长期未更新且引用资料较旧
### 📈 核心问题覆盖
[覆盖矩阵表]
### 🛠️ 建议的下一步行动
1. [最优先行动]
2. [次优先行动]
3. [可选优化]
是否需要我执行自动修复?(仅限:更新 index.md、修复明显拼写错误的死链)
```
---
## 硬约束
- **仅读扫描**:生成报告前,绝对禁止修改、删除、重命名任何文件
- **手动确认**:报告后等待用户选择要执行哪些修复,不可自动执行
- **可修复项**(需用户确认):
- 更新 `index.md` 添加未注册页面 / 移除幽灵条目
- 修复明确由拼写错误导致的死链
- **不可自动修复项**(仅报告):
- 创建新页面
- 判断论断是否过时
- 修改页面内容
- **操作日志**:用户确认修复后,在 `log.md` 顶部追加:
```markdown
## [YYYY-MM-DD] lint | 健康检查
- **结果**: 修复 N 处死链,修复 N 处索引,发现 N 个孤儿页面 [[PageA]]
```
⚠️ **写入前检查格式**:操作类型只允许 `ingest / query / lint`;新条目必须**独占一行**并以空行结尾——**经验**:上一条记录最后一行漏了换行时,两条记录会粘连在同一行。追加后回读一眼确认新条目开头是 `## [` 而不是接在旧内容后面。
---
## 关联连接
- [[AGENTS.md]] — Wiki 架构总规范
- [[index]] — 索引一致性检查的基准
- [[log]] — 修复日志记录