两个 OpenSpec 读不了的名仓库

HN Show AI Builds 2026-10-17T00:13:20.102140

OpenSpec 自己的写作指南早就定好了规则:每条需求只能有一个 SHALL,不写实现细节,错误场景要有对应场景,每个 spec 都要有真正的 Purpose。但它的校验器只检查语法,不检查这些规则。我们评估了 87 个项目、1725 个未关闭的变更,这个落差解释了大部分发现。

  1. 格式问题解决了,写作问题没有。几乎所有项目都能通过 OpenSpec 的语法检查:40 个项目里有 37 个,每条需求基本都有 SHALL 和场景。但通过检查不代表需求写得好。盲评和自动检查之间毫无关联(ρ = −0.02)。

  2. 团队会违反 OpenSpec 自己的写作规则,而且 spec 越大越严重。中位数项目里,只有 55% 的需求只陈述一项义务,只有 35% 为失败场景写了对应场景。项目越大得分越低,主要原因是需求把多项义务捆在一起,还直接点名源文件和函数。这种规模效应在两个独立样本中都出现了(ρ = −0.53 和 −0.52)。

  3. 需求一开始很聚焦,后来逐渐走样。只写提案的团队里,85% 的需求只陈述一项义务;而随机抽样中的主 spec 只有 56%。捆绑问题是在 spec 被合并和编辑的过程中出现的。

  4. Spec 落后于代码。有主 spec 的项目里,39% 的未关闭变更所有任务都已勾选,却从未归档,所以 spec 里不包含这些工作;三分之一的 spec 文件至今还带着归档时写入的占位 Purpose。很多团队把 OpenSpec 当成带 spec 的任务追踪器在用。

  5. 声誉不是质量信号。GitHub 星标数无法预测 spec 质量(随机抽样中 ρ = −0.03,25 个星标最多的项目中 ρ = −0.20),大家当作范例引用的项目,得分并不比随机项目高。OpenSpec 自己的仓库是我们找到的最好参考,在 40 个项目中排第 3,不过它自己 30 个未关闭变更中也有 23 个所有任务都已勾选却未归档。

  6. 小小的机械疏漏代价很大。两个知名仓库翻译了 OpenSpec 的标题,结果 OpenSpec 完全读不到它们的任何需求。第一部分里,8 个项目的 768 条需求躺在主 spec 的变更标题下面,OpenSpec 根本看不到。

团队能做什么

局限说明:这些是公开仓库之间的相关性,不是对照实验。阅读由语言模型按评分标准完成,两轮评分在超过 99.9% 的样本上差距不超过 1 分。仓库是 2026 年 10 月 15 日默认分支的快照。ρ(rho)是秩相关系数:取值范围从 −1 到 1,接近 0 表示没有关系。完整方法见文末。

四十个项目,逐一评分排名

随机抽取的 25 个项目、常被当作范例的项目、初步筛出的 4 个大项目,加上 OpenSpec 自己的仓库,全部用同一把尺子打分。

规格越大越差

我们用的每项指标都是「每条需求的比率」,所以项目大小本身既不加分也不扣分。但规模仍然比任何其他因素都更能预测得分。需求只有几十条的项目占据了榜首,而需求超过一千条的五 个项目全部排在后半段。

Spearman ρ = −0.53。横轴为对数刻度。得分为五个维度的加权平均值,按 0–100 百分位计(见方法部分)。流行度没有这种效应:得分与 GitHub star 数的相关系数为 ρ = −0.03。

这种下降是有具体指向的。清晰度和可测试性在任何规模下都站得住。真正退化的是原子性和「不碰实现」:在大项目里,一条需求开始承载三项义务,陈述里开始出现源文件名、内部类和函数签名。

每项指标与项目需求数量之间的 Spearman 相关系数,共 40 个项目。柱状条偏左表示大项目表现更差。J 指标来自盲评,S 和 C 来自自动检查。

按规模排名

规模的影响太大,所以我们只拿规模和它相近的项目来比。想进第一档,项目的整个 90% 置信区间都得落在所属规模区间的前半段;落在后半段就归入最后一档;其余居中。大多数项目都在中间:数据只能区分各档的两端,分不出同一档内部的先后,所以同一档里按字母顺序排列。

每根柱条显示的是该项目在自己所属档位内排名的 90% 区间,来自 1000 次 bootstrap 重抽样;横杠标记它在完整数据上的排名。档位划分:小型 3–15 个规格文件,中型 16–60 个,大型超过 60 个。

套格式是最简单的一步

OpenSpec 的工具会强制检查语法,效果也确实看得出来。40 个项目里有 37 个,几乎每条需求都写着 SHALL 或 MUST,并配了场景;场景基本都有 WHEN 和 THEN。可这并不能说明需求本身好不好。跨项目来看,盲审打分和自动检查打分之间毫无关系。

Spearman ρ = −0.02。每个点代表一个项目,两个轴都是这 40 个项目中的百分位得分。

盲审最常扣分的两类问题,全都能通过所有格式检查。下面这些例子是为本文编写的,用来展示这些模式,并非摘自任何项目。

常见缺陷

样本里反复出现的问题就那么几个。每一个找起来便宜,修起来也便宜。

以下为 40 个项目中出现该缺陷的比例,另有说明除外。“隐形”需求指的是留在 ## ADDED Requirements 下面的 ### Requirement: 块,或者主规格里出现第二个 ## Requirements 标题——OpenSpec 自己的读取器根本看不到它们。

大家常举的例子

我们在文档、博客、演讲、论坛,以及中日的相关文章里,找过那些被人拿来当作 OpenSpec 优秀范例的项目。找到的非常少。OpenSpec 的维护者点名的只有一个例子——OpenSpec 自己的仓库,而它也配得上这个提名:总榜 40 个项目里排第 3,中型档里排第 1。至于别人提到的寥寥几个第三方项目,表现和随机抽签没什么区别。

中位总体排名:showcase 项目 17,社区提名的 19.5,随机抽样 22,首次浏览样本 28.5。showcase 组和社区组规模都很小(分别只有 4 个和 6 个),所以这些中位数只能当作参考。

人气高的项目,规格写得更好吗?

并没有。OpenSpec 项目里星标最多的 25 个(从 2.8 万星到约 500 星),得分和随机抽样差不多;而且在这个群体内部,星标越多也不代表规格写得越好。

第一部分用的是随机抽样,所以多数是小项目,星标很少甚至没有。这里换个方向:挑出星标最多的 25 个仓库,前提是它们至少保留了 3 个主规格文件。每个项目都以第一部分的 40 个项目为固定标尺打分,因此分数可以直接比较。这 25 个里有 3 个已经出现在第一部分。

实际使用 OpenSpec 的热门仓库,比搜索结果看上去的要少。我们的搜索匹配到星标最多的 100 个仓库(全部超过 400 星),其中 49 个只是在 README、智能体提示词或配置文件里提到 OpenSpec,并没有 openspec/ 目录。真正保留 3 个及以上主规格文件的只有 28 个。

每个点代表一个项目的总体得分,横线是所在组的中位数。热门项目赢过随机项目的概率是 59%,这个差距完全落在随机波动范围内(Mann–Whitney z = 1.09)。把鼠标悬停在点上可以查看对应项目。

Spearman ρ = −0.20。星标取对数坐标。项目规模的影响和第一部分完全一致:按需求数量打分时,ρ = −0.52。

各维度得分中位数,以第一部分的标尺换算成 0–100 的百分位。热门项目的规格文件维护得更整洁(Upkeep),但每条需求写的场景更少,失败路径也更少(Scenarios)。

两个 OpenSpec 读不了的名仓库

星标最高的十个候选项目里有两个,studyzy/imewlconverter(10438 星)和 hawk86104/three-vue-tres(2447 星),把 OpenSpec 的结构化标题翻译成了中文:用 ## 需求 表示 Requirements,### 需求:,#### 场景:。正文可以是任何语言,但标题属于语法的一部分。OpenSpec 自带的校验器会拒绝这两个项目的每一个规格文件,所以在 OpenSpec 眼里,它们根本没有需求。我们无法给它们打分,只能按星标顺位取接下来的两个项目。

热门项目里最好的几个,水平和第一部分里的项目不相上下:aiogram/aiogram、Savory/Danet、justskiv/goschedviz、b310-digital/teammapper、dotnetfactory/fluid-calendar 和 SplashtopInc/winstall,放进第一部分的 40 个项目里都能排进前十。

另一头,我们能打分的项目里星标最多的 alibaba/druid,阅读得分比第一部分 40 个项目中的 36 个都低;而 microsoft/rayfin 的 46 个 spec 文件里,每五个就有一个过不了 OpenSpec 自带的校验器。

得分条显示的是相对固定基准的 90% 自助法(bootstrap)置信区间,竖线是得分。「可排位次」指该项目若加入第一部分的 40 个项目会排在什么位置。带 ¹ 的项目本来就属于第一部分,沿用第一部分的得分。两个无法打分的仓库列在最后。

从不归档的团队

有些团队只把 OpenSpec 用来写变更提案,从不归档,主 spec 目录因此一直是空的。这些提案写得比一般的主 spec 稍紧凑一些,但始终没有任何环节把它们整合成一份对系统的当前描述。

第一部分和第二部分只给 openspec/specs/ 打分,也就是团队一致认可的系统行为描述。变更提案放在 openspec/changes/<id>/ 里,以增量 spec(delta spec)的形式存在,直到执行 openspec archive 把它合并进主 spec。在我们的样本池里,有 130 个仓库只有变更,完全没有主 spec。我们按第一部分同样的随机抽取方式随机挑了其中 25 个,用同一套指标给它们的提案打分。这些分数描述的是提案而非当前规格,因此不计入前面的排名。

每个点代表一个项目。只有提案的项目,比随机抽出的主 spec 项目得分更高的概率是 63%,看着有苗头,但还不足以下定论(Mann–Whitney 检验 z = 1.56)。

每个项目占比的中位数。提案更常做到「一条需求只对应一项义务」,并且用更少的场景来界定每条需求。

把多项义务捆在一起是后来才出现的。在提案里,85%(中位数)的需求只写一项义务;第一部分的随机样本里这个比例是 56%。这是两组不同的项目,所以并非同一批项目的前后对比,但它和第一部分的结论吻合:需求一开始很聚焦,随着 spec 不断增长、反复修改,义务就慢慢堆了起来。

提案几乎只做新增。这 25 个项目里,处于活跃状态的变更累计包含 1,581 条 ADDED(新增)需求、76 条 MODIFIED(修改)、4 条 REMOVED(删除),RENAMED(重命名)一条都没有。不做归档,提案与提案之间就没人调和;项目真正的规格说明,其实就是所有未关闭变更的叠加。

大部分变更都能通过校验器。232 个活跃变更中,216 个没有任何校验错误;25 个项目里有 9 个至少有一个变更校验失败,最常见的情况是整个变更里根本没有增量规格(delta spec)。60 个符合条件的项目中,有 4 个写的变更规格压根不是增量,而是套用自己的模板或主规格的格式,OpenSpec 于是无从套用。

垫底的是 monid-ai/monid:4,235 颗星,是我们发现的 star 数最高的「只有提案」项目——35 个未关闭变更、323 条需求,从未归档,在原子性和盲评两项上得分都很低。Loft 2.1 的导入器只读主规格:项目没有主规格它就拒绝导入,并列出需要先归档的活跃变更。

「Valid(有效)」统计的是没有 OpenSpec 校验错误的活跃变更。分数条显示 90% 自助法(bootstrap)置信区间,重采样以整个变更为单位;「would place(排名)」指在 Part 1 那 40 个项目所用、不含 Upkeep 的标尺上的名次。点击列标题可排序。

一个附带规格说明的任务追踪器

大多数项目里,干活的地方就是 change 文件夹:一份提案、一份任务清单,方框一个个勾过去。而把已完成的工作沉淀成规格的那一步——openspec archive——恰恰是团队会跳过的。

第 1 到第 3 部分提示了一个规律:规格全靠归档生成、从没手写过;提案几乎只做新增;变更里完全没有规格内容。这里我们用计数来验证,不做打分,覆盖我们克隆下来的每一个项目:65 个带主规格的项目,112 个只有提案的项目。对每个未关闭的变更,我们读取它的 tasks.md 勾选状态和 delta specs。如果一个变更的所有任务都已勾选,却仍留在 openspec/changes/ 目录里,就说明这份工作已经做完,却没有回填到规格中。

按任务清单状态划分,各类项目中未关闭变更的占比。「Task list only(仅任务清单)」指有 tasks.md,但背后没有 delta spec。统计口径为 2026 年 10 月 15 日默认分支上的数据;已完成的变更也可能还在等评审或发布。

维护者自己也不例外:OpenSpec 仓库里 30 个未归档的变更中,有 23 个的所有任务都已勾选。已完成却未归档,未必就是疏忽。有的团队发布时才归档,有的则等评审通过。但只要变更还没归档,主规格描述的仍是这项工作落地之前的系统,所有读取这些规格的工具——包括 OpenSpec 自带的 show 和 validate——看到的都是旧行为。

在所有 155 个含未归档变更的克隆项目中,未归档变更最多、且任务列表已全部勾选的 15 个项目如下。点击列标题可排序。

修复 OpenSpec 仓库的五点建议

  1. 一条需求只承担一项义务。如果一条需求里出现了第二个 SHALL,那它多半是两条需求。样本中,正好只写一条 SHALL 的需求占比中位数为 55%。

  2. 别把实现细节写进来。文件路径、内部类名、函数签名应该放到 design.md 或代码里。哪怕代码被重写,需求也应当依然成立。

  3. 把 Purpose 写清楚。TBD - created by archiving change … 至今仍是 2,444 个规格文件中 866 个的 Purpose。改掉它只需要一句话。

  4. 做完就归档。任务全部勾选的变更就是已经交付了;在运行 openspec archive 之前,主规格描述的系统里仍然没有它。同时要正确合并 delta:主规格里残留的 ## ADDED Requirements 会让 OpenSpec 自己都看不到这些需求。

  5. 覆盖失败路径。包含非法输入、报错或拒绝场景的需求占比中位数为 35%。顺利路径恰恰是最不容易出错的那部分。

Loft 从同一批规格里看到了什么

Loft 是一种规格格式,同时提供命令行工具和 agent 技能(agent skill)。2026 年 10 月 15 日发布的 Loft 2.1 能把 OpenSpec 项目的主规格转换成 Loft 规格:loft import openspec 会将它们写入 spec/,作为一份产品契约,也就是结构化的规格说明,描述产品应当做什么,以及约束它的各项决策和限制条件。每个能力(capability)变成一个 Feature,每条需求变成一个 Requirement,每个场景变成一条验收标准(acceptance criterion)。

我们把 Loft 自家的检查排除在排名之外,避免打分偏向自家工具。说明一下:我们在全部 40 个项目上跑了正式发布的导入器,全都转换成功、没有报错,提取出的需求数量也和我们自己的提取器一致。中位数 94% 的陈述符合 EARS 句式(即类似“当……时,系统应当……”这种结构化写法),原因是 OpenSpec 里大多数需求本身就已经是这个形式——只要把 SHALL 直接读作 shall 就行。

第一次跑 loft check 时最常见的两类问题,恰好就是盲读评测里被扣分的两种模式。OpenSpec 把义务性要求写在场景文本里,loft check 会把它标成警告:40 个项目里有 38 个命中,共 41,116 条。此外,每个项目里它都会指出一些可能包含多条可验证陈述的语句。

loft-spec 技能的采纳流程是这样的:由编码代理(coding agent)把验收标准改写成测试能直接检查的句子,并补上占位用途;然后由相关责任人评审改动、确认结果。Loft 的开源版本可以免费试用,拿你自己的项目跑一跑。

我们如何打分

声明:Loft 是一家做规范(specification)工具的公司,该工具支持导入 OpenSpec 项目。评分方法在打分前就已写好;试跑之后所做的每一处改动都记录了原因。

全部 40 个项目

点击列标题可排序。跨档位的总排名基本反映的是项目规模,横向比较时请参考上面的分档。

查看原文