信任一个由 Claude 编写的包管理器,需要做些什么

本文由 AI 从英文翻译。阅读英文原文

做 jpm 的时候,我一行代码都没写,连读都没读过。jpm 是一个 JavaScript 包管理器,能用在 npm、pnpm、yarn 和 bun 项目里。这还包括我为了把它做小,让 AI 自己写 TLS、加密和 http 模块。一开始,这就是个地地道道的 vibe coding 项目。而且一个下午就做完了。那个状态下的它,我不该信任,其他任何人也不该。但接下来,我拿出自己做了 27 年多职业软件工程师积累的经验,让它对照现有的测试套件去测,又让它花五天时间自己写、自己狠狠地测,把它加固起来。

我的背景

这些年我用过很多编程语言,但大多是高级语言,比如 C#、Elixir 和 JavaScript。小时候我学 C,暑假去我父亲的公司帮他弄底层磁盘控制器,从那以后,我用低级语言写过的东西就没超过几个函数。所以要我上手 Rust,再用它写一个 60k+ 行的项目,短时间内是做不到的。我真正懂的是 npm、pnpm 和 bun 是怎么工作的,因为从 Node.js 0.8(2012)开始我就在工作中用 Node.js 了。我也知道软件到底需要多少测试,也知道把测试自动化、而不是只跑一次,有多重要。当年那些叫我先写测试的敏捷派,总算没白听他们的!

为什么做 jpm

我当时在看 YouTube 上一个叫 Syntax 的播客,主持人是 Wes、Scott 和 CJ。那一期一开头,他们聊的是 upm:一个又快又特别小的包管理器,不过是用 JavaScript 写的。我第一个想法是,说它小其实不成立,因为你还得带上 Node.js 运行时,或者别的什么运行时。upm 是 Pooya 写的,CJ 展示了项目自己给出的基准测试,说明它很快,但我就是想看看我们能不能做得更快。Pooya 是个很厉害的工程师,为 JavaScript 生态做了很多好工具,所以我知道这不会容易。最后,我那阵子一直在用 Opus 5.5 这个模型,它的表现让我非常震撼。所以我想,既然已经有那么多工具是用 Rust 写的(他们在播客里还拿这个开过玩笑),也许我可以用 Rust 做一个真正又小又快的包管理器。

在那期播客里,主持人在回答听众关于 AI 的问题,也是现在所有人都在问的问题。它能做出真正的软件,还是只能做 demo?工程师应该担心,还是应该兴奋?大多数人相信 AI 能一次就生成任何东西。剧透一下:确实能。只是如果没人主导、不懂软件开发,结果就不会好。

那个下午

于是那天下午,我正在收尾白天的本职工作,在终端里开了一个标签页,问 Claude 要把 upm 重写成一个 Rust CLI 需要做些什么。我看了一遍它给的概要,说了 OK,它就开干了。我记得大概只用了 45 分钟,它就有了一个能用的版本,不过用了一堆第三方 Rust crate,体积大了很多,而且只在一个很小的 package.json 文件上测过。为了把它做小,我让它分析到底是什么让它这么大,到那天晚上结束时,我已经有了自己的 HTTP、JSON、加密和 TLS 模块,二进制文件也小了很多。我确实要求它只写我们需要的部分,但也要狠狠地测试:能用其他项目的测试就用,不能就用我们自己的,再加上一些模糊测试。我想确保它们足够扎实。

那五天

接下来这部分,是所有在 “vibe coding” 做项目的人都会略过的。没错,它在一个小小的 package.json 文件上能跑,跑在 Windows WSL2 里的 Linux 上,但我没在 Mac 或 Windows 上测过,也没测过任何真正的大项目。于是我让它写了一个用来加固的脚本:拿 100 个、后来增加到 166 个大型公开项目,尝试安装和构建它们。然后再用这些项目自己用的包管理器安装和构建一遍,做对比。能跑测试的地方也跑它们的测试,因为有些测试需要机密信息或其他配置。在 Windows 命令行和 PowerShell 上也这么做一遍,同时我还在我的 Mac 上启动了一个 agent,在 Mac 上做同样的事。五天之后,在支持什么、怎么支持上做了很多决定,我们合并了 261 个 PR,得到了一个经过加固、测试充分的包管理器。

主导是什么样的

所以我没写代码,但我确实在主导它,做了很多小决定。比如 Claude 说换一种更快的锁文件格式只能省下几 ms,但我还是要求改了。Claude 还想把全局存储做成需要手动开启的功能,但我指出,如果 99% 的包都能用,那为什么要这样,于是它成了默认设置。我也不想让它运行脚本,包括依赖的安装脚本,除非你明确列出来说可以。我的想法是,现在大部分工作都是 AI 在做,所以在批准某个包的脚本之前,我完全可以先让它审查一遍有没有问题。

还有两次 Claude 真的提出了反对,但两次我都坚持了。

第一次是 TLS。我想把二进制文件做小,而 TLS 和加密的 crate 占了很大一块。Claude 说,这是你永远不该自己写的代码。我告诉它,那是老派的工程思维。我们用的是 Rust,所以像 Heartbleed 背后那种内存 bug 根本不在考虑范围内,而且我们一样可以狠狠地测试自己的版本,甚至测得更好。它承认自己夸大了风险,并列出了哪些测试套件能证明这一点(Wycheproof、NIST 测试向量、x509-limbo、BoGo)。我到现在还在怀疑这到底是不是个好主意,而且 Claude 喜欢拍你马屁,所以它说自己夸大了风险,也可能只是在顺着我。它通过了一大堆测试,包括根据其他项目的真实 bug 写成的测试。我知道,不管它是谁写的,我现在都会信任它得多。

第二次是 HTTP/2。我想要更快的速度,就问了这个。Claude 先用 curl 测了一下,说不值得做,因为 jpm 用 HTTP/1.1 完成的整个安装,已经比 curl 光是用 HTTP/2 下载还快了。我还是坚持要做。curl 得处理所有情况,而我们只需要向一个 registry 发很多 GET 请求。想赢过 bun,就得跳出常规去想。它同意了,大约两个小时后就有了一个能用的客户端。

然后我们做了测量。在我家里的网络上,HTTP/2 大约快了 17%。在 GitHub 的 runner 上,它要么打平要么输,冷安装 nuxt 时最多慢了 2 倍,处理大 tarball 时还多用了 8 到 15% 的 CPU。CI 才是安装真正花时间、花钱的地方,而且它还让二进制文件大了 60 KB。所以我让它把 3,671 行全删了。这一次是我错了,数据告诉了我这一点。

反过来的情况也有。如果你需要,jpm 会帮你安装 Node,而我本来想把 Node 的签名校验放在一个开关后面,因为我觉得它会拖慢安装。Claude 测出来只要 15 到 19 ms,还说做成开关就等于没人会打开它。现在它默认开启,我很庆幸,因为它增加了安全性。

为什么我信任没人敲过的代码

简单来说,靠的是 Wycheproof、node-semver、pnpm、yarn 和 bun 自己的测试,以及我们拿来测试它的 166 个仓库。下面是它们一路上发现的一些 bug:

  • node-semver 自己的测试发现了 25 处 jpm 结果不一致的情况,比如把 ^9007199254740991.0.0 当作版本范围接受。
  • npm-package-arg 的测试一开始只对上了 154 个中的 117 个。a@a.tar.gz 被当成了 tag。
  • 一次安全扫描发现,克隆来的仓库里的 .npmrc 可以让你的 registry 令牌经过它自己的代理发出去。概念验证截获了 Bearer USER-SECRET-TOKEN。现在项目的 .npmrc 已经无法降低安全性了。
  • 在真实仓库上,jpm 安装结果与 pnpm 不一致的依赖链接,从 1,320 个降到了 183 个;与 npm 不一致的,从 614 个降到了 14 个。
  • 在我真正上 macOS 测试之前,它在 macOS 上连编译都通不过。

做了这么多年软件,我也知道,不管写了多少测试、测得多充分,没有什么是没有 bug 的。信任是要靠挣来的,所以我打算以后在我所有的项目里都用它,也会持续支持修复,就算是 AI 提交的也一样。这不代表我会全部接受。它们需要用测试证明自己确实修好了某个问题。重点是,我不打算关掉 PR,但我会用 AI 来帮忙处理。

现在的状况

我们把可执行文件做到了 2MB,它不总是最快的,但排在最前面那一档。它也是最安全的之一,没有批准就不会运行脚本。发布还不到一天的包,不会被更新到最新版本。还有围绕 .npmrc 的那些安全措施。

下面是其中一张图表,展示了在 GitHub Actions runner 上,我们和其他包管理器相比能有多快:

GitHub CI,Linux 上冷安装 nuxt(591 个包),5 次运行的中位数:jpm 1.22 s、bun 1.28 s、pnpm 1.72 s、aube 3.00 s、upm 3.52 s、deno 7.64 s、yarn 9.63 s、npm 18.54 s

所有机器、阶段和项目的数据都在 getjpm.sh 上。

它不是最快的地方

说实话,这个基准测试规模很小,只有三个仓库。我们也不总是最快的。比如在 Windows 上装 nuxt,Defender 的扫描把速度拖垮了。在 macOS 上,nuxt 的热安装是 bun 和 aube 赢了。所以如果最终目标是速度,还有改进空间。我们确实在 Linux、macOS 和 Windows 上跑了 166 个项目的安装,还在 Linux 上随机跑了更多项目来找 bug,但我们并没有在 CI 和各台机器上逐个做性能测试。

jpm 完美吗?不。我 27 年来亲手写的任何东西都不完美,任何人写的任何东西也都不完美。完美并不存在。有够用,有出色,还有测试得足够充分、让你知道自己手里是哪一种。

在 macOS 和 Linux 上用 curl -fsSL https://getjpm.sh | sh,或在 Windows 上用 irm https://getjpm.sh/install.ps1 | iex 来试试,然后在任意项目里运行 jpm install。如果发现 bug,请到 GitHub 上提 issue。