
*当一个项目花费两年时间,去缩短“我知道这条规则”与“引擎知道它”之间的距离时,会发生什么。*
[](https://github.com/Automattic/harper)
[](LICENSE.md)




-ef6fd0)
## 为什么这个代码库以一种规则语言命名
我们着手回答一个问题 —— *harper 对 [Trellis](https://github.com/OpenCnid) 有用吗,以何种形式?* —— 并且我们拒绝仅凭 README 来回答它。因此,我们根据项目自身的记录对它进行了逆向工程:**4,460 次提交,2,266 个 pull request,226 个发布,135 位作者,时间跨度从 2023 年 10 月到 2026 年 7 月。**
答案最终并不是“语法检查器”,而是 **Weir**。
Weir 是 harper 的声明式规则语言,诞生于 2026-01-12 的提交 `46f4547f`。一条规则就是一个文件:
```
expr main (a couple of more)
let message "The correct wording is `a couple more`, without the `of`."
let description "Corrects `a couple of more` to `a couple more`."
let kind "Redundancy"
let becomes "a couple more"
test "There are a couple of more rules that could be added, how can I contribute?" "There are a couple more rules that could be added, how can I contribute?"
```
这就是全部内容。匹配、元数据、修复 —— **以及它的测试,都在同一个产物中。** 一个构建脚本会扫描目录并生成注册表;一个生成的测试框架(harness)会运行每个已发布规则的断言,而无需任何人维护测试文件。在六个月内,**所有添加规则的提交中有 42.6% 选择使用 Weir,而不是手写的 Rust。**
有趣的是,同样的设计使得诚实检查成为可能。我们对此进行了测量:**351 条规则中有 64 条完全没有提供断言,而生成的测试依然通过了。** 一个自我测试的产物需要在*注册时*就设定最低底线,而不仅仅是一个运行器。这个发现对我们来说,远比它的语法检查功能更有价值。
## 阶梯
Weir 并非凭空出现。它是整个项目历史中最清晰发展轨迹的第四级阶梯:
| 阶梯 | 诞生时间 | 发生了什么改变 |
|---|---|---|
| 手写 Rust | `309d840e`, 2024-01-15 | 每条规则都是一个定制的扫描器 |
| `Pattern` | `6107594e`, 2024-09-01 | 一个封闭的匹配器代数;规则声明一种形状 |
| `Expr` | `a8fb0c6d` (#1393), 2025-06-13 | 匹配返回的是一个 **`Span`** 而不是长度 —— 因此一次遍历就可以服务于 279 个规则文件 |
| **Weir** | `46f4547f` (#2357), 2026-01-12 | 规则变成了**数据**,携带其自身的测试 |
| **Weirpack** | `3a5cd68b` (#2491), 2026-02-03 | 规则*集合*变成了一个**可分发单元** —— 包含清单、规则以及可选字典 |
每一级阶梯依然建立在下一级的基础之上。没有任何东西被替换 —— 而[这种不替换本身就是一个发现](docs/density-chain/DENSITY-CHAIN.md#c2)。
## 这里面有什么
| 路径 | 它是什么 |
|---|---|
| **[`docs/density-chain/DENSITY-CHAIN.md`](docs/density-chain/DENSITY-CHAIN.md)** | **地图。** 一条主干加上九条分支,每条分支都是五个层级的密度链条。共 16,000 字,解决了每一个声明。 |
| [`docs/density-chain/DENSITY-CHAIN.html`](docs/density-chain/DENSITY-CHAIN.html) | 同样的内容,渲染后的版本 —— 支持主题、独立完整、无外部请求 |
| [`findings/`](findings/) | 五项技能输出,按运行顺序排列:SPARK 诊断、子代理组合、评审团、复杂性大会 |
| [`findings/branches/`](findings/branches/) | 九位绘图者的返回结果,未经编辑 |
## 方法,用一段话概括
九个只读的子代理,每个负责一个子系统类别,它们从一个逐字节相同的 ground block 和严格的五层返回框架中并行生成。它们谁也无法看到彼此的输出。每一个子代理必须在**每一个**定量声明中附带 `path:line`(路径:行号)、提交 SHA 或 PR 编号,必须填充 `## Uncovered` 字段以确保缺陷不会在沉默中被掩盖,并且被禁止运行 `cargo`、`pnpm`、`just` 或 `npm`。主干和交叉链接是在事后由 orchestrator 组合而成的,因为一个同级子系统去推测它无法看到的类别,恰好会产生这种规则纪律旨在防止的、无法协调的声明。
总运行量:**1,283,821 个 token,558 次工具调用,执行了零行 harper 代码。**
## 我们发现的 harper 维护者可能会关心的问题
作为观察结果提供,而不是作为补丁 —— 关于我们为什么没有提交 issue,请参阅 [`AGENTS.md`](AGENTS.md)。
每一项的日期均为 2026-07-23/24,均源自于阅读代码,而不是来自执行出的反例:
- **`PatchCriteria::WordIs` 在压缩字符时没有进行长度检查**,使其变成了一个前缀匹配 —— 并且已发布的 tagger 模型中 201 个补丁里有 158 个都要经过它。([C6](docs/density-chain/DENSITY-CHAIN.md#c6))
- **`statsPath` 被赋值给了 `base.file_dict_path`**,因此统计信息的位置从来都是无法设置的,而且错误提示至今依然写着 "fileDict"。([C8](docs/density-chain/DENSITY-CHAIN.md#c8))
- **`WordId` 使用有损的 64 位大小写折叠哈希作为字典的键**,因此规范拼写遵循“后来居上”原则。(已知问题:issue #2411。)([C5](docs/density-chain/DENSITY-CHAIN.md#c5))
- **`harper-desktop/.github/workflows/` 位于仓库根目录之下**,因此 Actions 从未读取过它 —— 并且它还调用了两个不存在的配方(recipes)。([C8](docs/density-chain/DENSITY-CHAIN.md#c8))
- **同一个 crate 中存在两个 TLD 表**,分别包含 15 和 106 个条目。([C1](docs/density-chain/DENSITY-CHAIN.md#c1))
## 想要 harper?只需一条命令,直接从源码获取
我们不托管他人的代码:
```
git clone https://github.com/Automattic/harper.git
```
或者直接使用它 —— [writewithharper.com](https://writewithharper.com)。
## 引用人类,而不是我们
```
@misc{adams2023sparsedense,
title = {From Sparse to Dense: {GPT-4} Summarization with Chain of Density Prompting},
author = {Adams, Griffin and Fabbri, Alex and Ladhak, Faisal and Lehman, Eric and Elhadad, No{\'e}mie},
year = {2023},
eprint = {2309.04269},
archivePrefix = {arXiv},
primaryClass = {cs.CL},
url = {https://arxiv.org/abs/2309.04269}
}
@software{harper,
title = {Harper: Offline, privacy-first grammar checker},
author = {Potter, Elijah and {The Harper Contributors}},
year = {2026},
url = {https://github.com/Automattic/harper},
note = {Apache-2.0}
}
```
## 诚实的说明
- **没有执行任何代码。** 所有的测试计数都是对源码中 `#[test]` 属性或 `test` 行的统计 —— *而不是一次实际的绿色通过运行*。上面指出的每一个缺陷都是通过阅读代码得出的。
- **可达性是工作空间范围的。** “没有非测试调用者”是指在 harper 自己的 workspace 内没有调用者。
- **T5 记录的是请求,而不是承诺。** 一个开放的 PR 意味着有人提出了需求,而不是维护者已经同意。
- **这是快速移动目标的快照。** Harper 每月大约合并 60 个 PR。“前沿”部分衰减得最快;有些可能已经过时了。锁定版本为 `efa59c33`,于 2026-07-24 验证。
- **由人类和 AI 共同撰写。** 阅读、计数和起草工作由 Claude 在 [OpenCnid](https://github.com/OpenCnid) 的指导下完成;而提出的问题、设定的范围以及关于什么才是重要的判断,均归于所有者。如果地图有误,那是我们的责任去修复。
*两年的提交,一种规则语言,以及一个无需提出任何要求就能通过 64 次的测试套件。*