仓库不只是「一个项目」。它构建某种东西,而它构建的是什么,决定了对它提出哪些要求才算合理。已发布的库理应不提交依赖锁文件——它锁定的版本,任何安装它的人都会忽略。目录中与它并列的应用程序则相反,因为它锁定的版本正是被部署的那些。用同一条规则衡量两者,必然会读错其中之一。
在指标 2.3.0 之前,这个问题一直由单一的替代信号代答:仓库是否向注册表发布了软件包。按此标准,PyPI 上的每个命令行工具都会被读作库,顺带发布了一个辅助包的每个应用程序同样如此。
证据所支持的全部读法
分类是多标签的。既是已发布的库、又是可运行的工具,这是常态,而不是需要消解的矛盾:ripgrep 既是 crate 也是可执行文件,esbuild 既是 npm 包也是可执行文件。因此仓库会保留其证据所支持的每一种读法。
词汇表是一棵两级树。顶层回答真正要紧的问题——软件如何被消费——一个仓库可以携带不止一个分支:
| 顶层 | 由此可以合理期待 |
|---|---|
| 库 — 作为代码被引用 | 稳定的接口、变更日志、其他软件可以依赖的版本管理 |
| 应用程序 — 作为程序被运行 | 锁定的依赖、部署与配置的规范 |
| 宿主扩展 — 被安装进宿主 | 宿主提供信任模型与发布节奏 |
| 笔记本 | 以上皆非——它由人阅读和运行,不被任何东西导入,也不安装到任何地方 |
两者同时成立时,两者都成立:混合型软件承担它所携带的每一种读法的义务,而不是其中最宽松的一种。
子类型
只有当更细的答案会改变读者的合理预期时,子类型才存在:应用程序的界面形态,或扩展从中继承信任面的宿主类别。
| 应用程序 | 宿主扩展 |
|---|---|
| 命令行工具 | 插件 |
| 终端界面 | 浏览器扩展 |
| 桌面 | 编辑器扩展 |
| 移动 | 主题 |
| 网页界面 | |
| 网络服务 | |
| 聊天机器人 | |
| MCP 服务器 |
库刻意没有子类型。 早前的版本区分框架、SDK、API 客户端、中间件和驱动——而这些边界并不存在:axios 既是 API 客户端也是库,flask 既是框架也是库——这是没有正确答案的问题。库连接到什么,会作为集成事实记录在别处,而不是这里。
答案可以停在顶层。 Cargo 的二进制目标和 Go 的 cmd/ 布局证明仓库构建的是 可执行文件——但证明不了它是命令行工具还是服务器。这样的仓库就被简单归类为 应用程序,只有当子类型有自己的证据时才会获得子类型。有证据支撑的宽泛答案,胜过只能靠猜的具体答案。
结论是怎样得出的
证据按可信程度分级,任何单一的弱信号都不足以产生标签。
| 证据 | 例子 | 分量 |
|---|---|---|
| 构建清单中的直接声明 | npm 的 bin 条目、console_scripts 入口点、<OutputType>Exe</OutputType>、Composer 的 type、Maven 的 <packaging>、Cargo 的 [lib] 目标 | 决定性 |
| 注册表的声明 | Packagist 类型、NuGet 包类型、crates.io 分类 | 强 |
| 已声明的依赖 | 网页框架、CLI 参数解析器、聊天平台客户端 | 中等 |
| 文件树结构 | cmd/…/main.go、src-tauri/、Helm chart、浏览器扩展清单 | 辅助 |
| 仓库主题与注册表关键词 | cli、wordpress-plugin、self-hosted | 弱——自行标注 |
| 仓库描述 | 「a CLI for…」「a library for…」 | 弱——自行标注 |
前两类分量最重,因为它们不是意见:写下 <OutputType>Exe</OutputType> 的人并不是在描述软件,否则构建根本无法运行。主题是意见,两个主题彼此印证是能够承载任何结论的最低限度。
证据也会朝反方向起作用。带 publish = false 的 Cargo 包、type 为 project 的 Composer 清单、.NET 工具、Maven 的 war——每一个都排除了「可供其他软件依赖」这一读法,无论仓库在其他方面看起来如何。
没有结论的情况
三种情形不会产生分类,而且三种都很常见:
- 报告早于分类功能。 每一份已存储的报告都依据它本就包含的事实完成了重新分类,但清单证据是在扫描过程中采集的,因此更早收集的报告只带有较弱的层级,并将在下一次检查该仓库时获得完整读法。
- 证据无法回答这个问题。 许多仓库没有发布清单,没有信息量的主题,自我描述的文字也没有陈述任何结构性事实。
- 给出结论就等于猜测。 有信号但不足以越过阈值时,不作任何断言。
这三种情况下,报告都只是不显示分类。缺失从不被呈现为一项发现,不会据此进行任何评分,没有分类的仓库也不会被标记、降级或以任何方式受罚。
构建多个产物的仓库
一个既有可部署服务、又有三个已发布库的 monorepo 并不是单一产物,把它压缩成一个答案会丢失「两种读法都成立」这一事实。因此每份清单都按其自身的声明分类,仓库则承载这些结果的并集。
有一点值得直说:这类仓库显示的单一主要标签是证据最充分的那一个,而在大型 monorepo 中,它可能是某个已发布的构建工具,而不是该项目为人所知的产品。主要标签用于展示以及对可比仓库分组,它从不构成任何评分决定的依据。
目前有什么影响
影响一项检查,并遵循一条常设规则。自指标 2.5.0 起,安全态势 回退机制中的依赖锁文件期望会读取分类:清单中声明了可执行文件的已发布软件包按应用程序对待并保留该期望——此前仅凭发布这一事实就会免除它。
常设规则是:评分只读取清单声明。从文件结构、主题或描述推断出的标签会被展示和记录,但绝不移动分数——只有构建清单直接陈述的内容(bin 条目、控制台脚本入口点、OutputType)才可以。分类的其余部分仍然是数据:每份报告中的 metrics.classification,连同产生它的证据。后续的接入将在落地时记入 方法论版本。