wiki-lint 是做什么的?
首次运行前,请先阅读工具包中的
docs/GOTCHAS.md(已知误报及操作说明)。
维基(wiki.config.json 中的 wiki_root)的只读健康扫描,外加三个修复工具。全部基于 Python 标准库,在 Windows、macOS 和 Linux 上行为一致。每条维基特定规则(文件夹、状态分类、字段映射、豁免项、动词列表、占位符模式、阈值)都存放在配置中,因此当维基新增字段、文件夹或检查项时,逻辑代码无须修改。设计上在超过 3000 条笔记时仍能保持快速(完整运行只需数秒,除检查 13 在如此规模下需要约半分钟)。
相关技能
wiki-ingest,wiki-ingest-book:写入本技能要检查的笔记(使用相同的配置来决定状态和索引路由)。wiki-synthesize:检查 15 生成其工作清单;检查 32 审计其印章。vocabulary-update:检查 30 的修复工具。
布局
skills/wiki-lint/
SKILL.md
scripts/
lint.py # 运行检查(--only, --out-dir, --self-test)
fix.py # 修复工具:索引提案、检查 12 反向链接、提升草稿
references/
check-origins.md # 每条检查存在的原因(单一权威)
cli-reference.md # 标志、配置键、输出文件、报告格式
wikikit/lint/ # 实现:context.py, checks_*.py, fixes.py, defaults.json
tests/fixtures/lint/ # 练习每条检查的极小维基 + expected_counters.txt
各类事实的存放位置(切勿在这些位置之间重复):references/check-origins.md = 检查为什么存在;本文件 = 检查做什么;wikikit/lint/checks_*.py = 检查如何实现;tests/fixtures/lint/expected_counters.txt = 为什么夹具值是这个值。在一次实况运行的末尾,工具会为每条触发的检查打印其 check-origins.md 指针,这样当你判断一条命中是真实还是噪音时,理由就已经送达。
工程不变性(不得破坏)
- 所有文件 I/O 均通过
wikikit.textio(字节级 UTF-8,无 BOM,行尾永不转换)。切勿使用默认换行处理的文本模式open():这样读取并重写一个 CRLF 文件会静默将其转换。 - 切勿在可能包含 CRLF 的文本上使用带
$的多行正则表达式:$位于 CR 和 LF 之间,会拆分这对字符。逐行编辑(frontmatter.set_key,fixes._insert_backlinks)。 - 配置是唯一的真理来源。 当维基新增字段、文件夹、状态或豁免项时,编辑
wiki.config.json(lint部分),而非代码。默认值位于wikikit/lint/defaults.json。 - 存根豁免是统一的。
lint.stub_exempt_checks列出了跳过status: stub笔记的检查编号;从配置中读取该列表,切勿在正文中重复。检查 10 和 11 内联排除了存根(文件系统计数)。 - 占位符检测以未解析为条件。 在仓库索引中能解析到的维基链接目标绝不被标记为占位符,即使其名称匹配模式;合法的全大写缩写笔记不得触发标记。
- 在发布逻辑变更前必须先做自检。 对检查的任何修改(新增检查、更改阈值、重构启发式)都必须通过测试:
python skills/wiki-lint/scripts/lint.py --self-test。当变更有意时,同时更新tests/fixtures/lint/和expected_counters.txt。证明夹具具有区分能力:还原逻辑变更,重新运行,并确认计数器发生变化。前后都能通过的夹具什么也保障不了。 - 零反映的是检测器的度量,而非维基的度量。 在信任一条新检查的零值之前,先注入一个阳性案例并观察它是否触发。
- 警告标记的所有权是位置的,而非正则表达式链式(检查 22)。 维基可能同时使用
[[Paper]]⚠和⚠[[Paper]]。一个符号的所有者是:如果左端去除空白的文本以]]结尾,则为其左侧的链接;否则为其右侧的链接;与任何链接都不相邻的符号是正文中的谨慎标记。不要使用两个链式正则表达式来解决这个问题(第二个会吞掉相邻链接的标记,静默地取消标记一个未验证的来源)。对标记的任何批量编辑都需要在操作前后对每个引文键进行普查。
检查项说明
| # | 名称 | 标记内容 | 豁免项 |
|---|---|---|---|
| 1 | 孤立概念 | 没有任何论文/书籍的反向链接(内联链接或 concepts_touched)且无兄弟概念的反向链接(内联或 related_concepts);兄弟层级防止综合枢纽误报 | stub |
| 2 | 孤立方法 | 没有论文/书籍的反向链接且没有指向任何论文/书籍的出站链接 | stub |
| 3 | 空的 concepts_touched | 论文笔记中该字段为 null、空或 [](也是批量导入时前置元数据损坏的预警) | stub |
| 4 | 无来源声明 | 概念笔记中至少 unsourced_threshold 行包含声明动词(verb_list)且在 +/-unsourced_radius 行内无维基链接,也不在任何有维基链接的标题下 | stub |
| 5 | 损坏/占位符维基链接 | 目标不在全仓库索引中(broken),或匹配 placeholder_wikilink_patterns 且无法解析(placeholder),或为空([[]], empty-target) | link_target_ignore_patterns(Bases 嵌入) |
| 6 | 草稿 | promote:引用至少 min_distinct_sources_for_final 个不同源作品(一本书及其各章算一个来源)的草稿。stale:低于该阈值且早于 stale_days 的草稿,或未标注日期的草稿 | 无 |
| 7 | 索引覆盖率 | 笔记的基本文件名不在 index.md 的维基链接中 | stub;当 index_mode 为 bases 时跳过 |
| 7b | 索引章节位置错误 | - [[X]] 出现在错误的 ## 章节下(index_sections) | 在 bases 模式下跳过 |
| 8 | 书籍 chapters_ingested 同步 | 声明的集合与磁盘上的 {book}_ChNN.md 文件不一致 | stub 章节 |
| 9 | 章节缺少 book: | *_ChNN 笔记中 book: 链接缺失或无法解析 | 无 |
| 10 | Quick Stats 漂移 | ## Quick Stats 中声明的计数与对应文件夹(quick_stats_labels)的实际文件数不一致 | 无 |
| 11 | 按字段覆盖率漂移 | ## Coverage by Field 中概念/方法列与实际非 stub 计数不一致(字段行按 field:,-> 子字段行按 subfield: 以 [[MOC_*]] 链接为键;有笔记但无行的子字段标记为 MISSING-ROW) | stub;无字段笔记为 INFO |
| 12 | 双向 concepts_touched | paper -> concepts_touched[token] 中目标缺少 [[paper]] 反向链接(asymmetry,可自动修复)或目标不是有效笔记(missing_target,需要排查标记) | 无 |
| 13 | 别名无链接 | 一个已记录的别名(独特缩写或多词短语)在其他概念/方法的正文中被使用,但没有指向其规范笔记的链接。INFO | stub;alias_link_stoplist;不搜索引用块 |
| 14 | 语言雷达 | 标题(H2-H6)使用非维基语言,或在正文中至少命中 prose_threshold 个停用词。仅报告;除非 language_radar.enabled 启用 | stub |
| 15 | 综合雷达 | 对于 final 概念/方法:missing = 满足知识状态块条件但无此块;stale = 块早于其引用的最新作品的 last_verified | 非 final |
| 16 | 状态词汇 | status 不在 status_by_type[type] 中,status 或 type 缺失,或模板泄露(draft | final) | 无 |
| 17 | 无 source_verification | 论文缺少印章;以 abstract 开头的值不算印章。INFO 积压 | stub |
| 18 | 已验证但不完整 | 已加盖印章的论文缺少 volume 或 pages,且没有带日期的 check18_exempt: "YYYY-MM-DD, reason";会议论文和学位论文按场所和形式豁免 | stub |
| 19 | evidence_level 不在词汇表中 | 值不在 evidence_level_vocab 中(仅论文) | 无 |
| 20 | 有用引用 ]] | ## Useful Quotes 下的引用块中包含泄露的 ]] | 无 |
| 21 | 全文前群体 | 日期早于 synthesis.prefulltext_cutoff 且没有全文级别印章的论文。INFO | stub |
| 22 | 过期的全文前标记 | 一个 ⚠ 其所属维基链接指向的论文已有全文印章,经过三次尾部豁免(正文说明、带日期的斜体注释、(N src 置信度)。确实存在误报 | 无 |
| 23 | 未填充的声明占位符 | 匹配 unfilled_claim_patterns 的行(提到了来源但未写出声明) | 无 |
| 24 | 已弃用的前置元数据键 | deprecated_frontmatter_keys 中的键(默认 journal 改为 venue);精确匹配,无误报 | 无 |
| 25 | 未引用的统计数字 | 统计数字(numeric_claim_patterns)在半径内没有维基链接,分为 cited / block / methods / domain 类。INFO。无任何豁免 | stub;跳过引用块 |
| 26 | 表格列数不匹配 | 行单元格数与分隔行不一致的表格(唯一的结构检查) | 无 |
| 27 | 乱码 | 通过 encode('cp1252').decode('utf-8') 往返转换后结果不同的行 | 无 |
| 28 | 有印章但无 last_verified | 有 source_verification 但无 last_verified 的论文/书籍(对检查 15 的 stale 不可见)。INFO | 无 |
| 29 | 仓库外别名链接 | 指向 memory_slug_prefixes 中别名的维基链接,全仓库范围。除非列表非空,否则禁用 | memory_slug_exempt_files |
| 30 | 词汇同步 | _VOCABULARY.md 缺少某个活跃概念/方法(missing)或持有一条已删除笔记的行(ghost)。INFO;修复工具 /vocabulary-update | 无 |
| 31 | 有印章但无文本 | 最多 60 个字符的印章,且在任何通道中均无描述性的验证文本。INFO | 无 |
| 32 | 配对无印章 | (笔记,在其知识状态块中引用的作品)配对中,来源笔记的印章未提及该笔记。阻塞性;声明为债务(seal_debt_file)者会报告但不计入 | 声明的债务 |
| 33 | 无标题或场所 | title 或 venue 为空的论文 | stub |
调用方式
从仓库(向上查找最近的 wiki.config.json 作为配置):
python skills/wiki-lint/scripts/lint.py # 全部检查;列表输出到 _temp/
python skills/wiki-lint/scripts/lint.py --only 5,12 # 选定检查
python skills/wiki-lint/scripts/lint.py --self-test # 夹具 vs expected_counters.txt
python skills/wiki-lint/scripts/fix.py check12 --dry-run
python skills/wiki-lint/scripts/fix.py promote _temp/promote_batch.txt
python skills/wiki-lint/scripts/fix.py index # 需要 index_mode "manual" 且之前运行过 lint
标志、配置键以及每条检查写入的文件:references/cli-reference.md。
何时编辑什么
- 新增维基文件夹(例如
datasets/):将其添加到wiki.config.json的folders中。它必须在笔记创建之前注册,否则指向它的每个链接都会被标记为损坏。 - 新增状态值:将其添加到
lint.status_by_type.{type}。新增笔记类型:添加类型及其允许的状态。 - 概念/方法的新字段:将别名添加到
field_titlecase_map。 - 细化检查 4:
lint.verb_list。放宽/收紧其容差:lint.defaults.unsourced_radius。 - 新的占位符模式:
lint.placeholder_wikilink_patterns(记住它仅适用于无法解析的目标)。 - 某条检查应跳过存根:将其编号添加到
lint.stub_exempt_checks。 - 开启语言雷达:将
examples/languages/es.json(或自己编写列表)复制到lint.language_radar。 - 新增一条检查:在
wikikit/lint/checks_*.py中实现,将其计数器添加到expected_counters.txt,添加可练习它的夹具,在references/check-origins.md中写入其条目。动机未记录在案的检查在首次看起来有噪音时就会被禁用。如果动机无法确定,请写入“未记录”;切勿凭空编造。
安全规则
- Lint 对维基是只读的;它仅将每条检查的列表写入
--out-dir(默认_temp/)。 fix.py仅在报告位置(反向链接、状态)修改笔记,保持每个文件的行尾,并记录每次更改以便回滚。- 当不确定某个启发式更改是否正确时,先编写夹具,观察自检失败,然后再进行更改。
- 缓存目录永不纳入版本控制(
_temp/等始终留在.gitignore中)。版本化的度量值几个月后会被当作今天的数值来解读。