---
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` 登记来源，再 `--emit-doc` 刷新 `TAGS.md`。
   - 两者都不成立 → 删掉这个 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]] — 修复日志记录
