一月份的时候,我偶然看到一篇博文,作者是one-agent-one-browser的开发者。这是一个完全用 Rust 语言编写的浏览器,由一个人指导一个编码代理,在几天内完成。不过,它不支持 JavaScript。我读完之后心想:“用同样的方法构建一个 JavaScript 引擎能有多难呢?”
六周后,JSSE(JavaScript Simple Engine)成为我所知的第一个100%通过test262非预发布环境测试的JavaScript引擎:涵盖所有98,426个场景,包括[此处应填写具体框架名称]、[此处应填写具体框架名称]、[此处应填写具体框架名称language/]和[此处应填写具体框架名称]。它不是V8,不是SpiderMonkey,也不是JavaScriptCore。这是一个完全用Rust从零开始构建的引擎,由Claude Code以YOLO(You Only Live Once,你只活一次)的方式开发。built-ins/``annexB/``intl402/
我一行 Rust 代码都没写。真的,一行都没有。在我看来,这个仓库只是一个只写数据存储区。我甚至都没手动创建 GitHub 仓库;也是代理自动创建的。
它的起源
一切始于聊天室里关于代理程序编码和浏览器的对话。1月27日下午2点42分,我提出了一个想法:“好,我们是不是应该从零开始开发一个JS引擎,然后把它集成到浏览器里?”17分钟后,我找到了单页规范,并从中获得了关键信息:“最棒的是我们有test262测试用例,所以我们可以创建一个反馈循环,从而创建一个能够通过所有测试的JS引擎。”
下午 3 点 05 分,代码库上线了。我搭建了初始框架(CLAUDE.md,,PLAN.md技能),并在下午 3 点 59 分启动了一个Ralph Loop,并发出大量提示,使其能够自主地逐个任务实现引擎,运行 test262,审查代码并提交。
16:02,第一次提交成功。我去开会了。一个小时后,17:09,JavaScript 代码已经开始执行了:
target/release/jsse -e 'function Foo(x) { this.x = x; } var f = new Foo(42); console.log(f.x);'
42
当晚19:10,test262测试通过率达到了17.63%。我结束了当晚的工作。从零开始,用Rust语言写出了一个能正常运行的JS引擎,大概用了四个小时。
数字
| 指标 | 价值 |
|---|---|
| 开始日期 | 2026年1月27日 |
| 100% 非分期测试262 | 2026年3月9日(42天) |
| 总提交次数 | 592 |
| 完全锈蚀 | 约17万行 |
| 新增线路(所有时间) | 929,475 |
| 已移除线路(所有时间) | 448,317 |
| 我实际操作键盘的时间 | 总共约4小时 |
最后一个数字最令人惊讶。总共四个小时,分散在六周内完成:有时是30秒,有时是2分钟。其余时间都是智能体自主运行的。
它是如何建造的
配置刻意保持简洁。Claude Code 以 YOLO 模式运行(自动接受所有工具的使用)。没有进行任何复杂的编排。我使用的插件有:
- **
/simplify**可能只用过一两次,当文件太大需要重构时才会用到。 - **
ralph-wiggum-loop**内置的自主循环插件。我一开始用的是这个,但发现过一段时间后它会丢失一些设置(下文会详细说明)。 - **chiefloop.com**是一个用于夜间运行 Claude Code 的外部工具。它适用于长时间无人值守运行,但缺少一些我常用工作流程中的功能。
项目的核心是PLAN.mdClaude 根据 ECMAScript 规范和 test262 子模块自行生成的任务列表。我通常的提示是:“阅读PLAN.md中第一个未完成的条目,制定计划并实现它,将其标记为已完成,提交并推送。” 仅此而已。我从不审查代码,从不审查计划,也从不查看差异。PLAN.mdCLAUDE.md的规则是:不允许出现回归,并且始终尝试通过比之前更多的测试。有时,当 Claude 试图跳过它认为太难的任务时,我会提出异议:“你为什么要跳过这个?我们必须实现所有内容,所以最好现在就解决它。” 但我的指导仅限于此。
最长的无人值守循环成功运行了 16 小时,用于实现 Temporal API。我在睡前启动了它,回来后发现它已经完整实现了InstantIANA时区支持(通过 ICU4X ZonedDateTime)和12个日历系统,并完成了 4482 项测试,其中 262 项测试通过。Temporal 是 ECMAScript 近期历史上规模最大、最复杂的提案之一,而代理仅用一个通宵会话就完成了它。PlainDateTime``PlainDate``PlainTime``Duration``Temporal.Now
实际的提示是什么样的
上一节抽象地描述了工作流程:计划、实施、测试、提交。但实际的日常工作中,这个过程究竟是怎样的呢?以下是项目中的一些真实案例,它们本身就讲述了一个故事。
出现次数最多的提示语——可以说是整个项目的核心,出现了20多次——是这样的:
请查看PLAN.md及相关 plan/ 文件,并提交对 test262 通过率影响最大的下一个功能。提出三个可能的功能及其各自的影响,一旦我们了解了这些影响,就可以选择一个进行开发。
这就是核心流程。我并不负责选择具体工作内容;我要求代理人分析 test262 的覆盖率缺口,提出按影响程度排序的备选方案,然后我再从中选择一个(或者直接说“开始”)。我的大部分互动都停留在这个层面:战略层面,而非战术层面。
当事情出现偏差时,提示信息就转向了调试:
发生了什么事?你想做什么?为什么这个 bash 脚本运行了 40 分钟?
长时间运行的会话有时会在某个测试上停滞不前,或者陷入编译循环。解决方法通常是终止会话,然后以更小的范围重新启动。
随着项目的成熟,我对并行计算的要求也越来越高:
不如我们把这些任务分别分配给不同的代理团队,让他们各自负责规划和实施,每个代理在自己的工作树中工作,完成后再把所有工作合并起来?这样大概能得到 300 个新的流程?
我们先创建 3 个计划,然后我会安排 3 个代理人分别在不同的工作流程中执行每个计划。
Claude Code 支持工作树——多个代理可以同时工作的独立 Git 分支。最难的功能就是这样实现的:将工作拆分成独立的轨道,并行运行,然后合并。Temporal API 的实现大量使用了这种模式。
有些提示简直就是马拉松式工作的信号:
是的。开始并完成所有阶段,并在过程中做出承诺。
我们或许应该直接着手处理方案 A,不要回避。先制定一个处理数组空洞的计划,然后付诸实施。
最后一个问题与数组空洞有关——这是 JavaScript 最棘手的语义难题之一。例如[1, , 3],数组 a 在索引 1 处有一个空洞,它的行为undefined与 b 数组在一些细微的、规范规定的方面有所不同。代理程序处理了这个问题,但这需要专门的会话。
游戏后期的提示(98%以上)最有趣,因为工作性质完全改变了。我们不再是实现功能,而是寻找单个测试失败的原因:
我们离完全合规只差一步之遥了。还缺什么呢?我们先找出第一个未达到100%达标的类别,然后解决它。
以下哪项措施能够使特定类别达到 100% 合规?
最后冲刺到100%:
我们有一份 @PLAN.md 文件和一系列 @plan/ 文件。我们的最终目标是 100% 符合 test262 标准。我们目前已接近目标,但仍需弥补一些不足。您的任务是制定一个计划,帮助我们实现 100% 符合标准——稳步前进,避免任何倒退。
所有这些案例的共同点是,我从未告诉过代理如何实现任何事情。我只是设定目标、选择优先级,偶尔排除一些障碍。代理负责具体的工程设计。
通过率曲线
图表比我描述得更清楚。以下几个里程碑值得一提:
| 日期 | 速度 | 发生了什么 |
|---|---|---|
| 1月27日 | 26% | 项目启动:词法分析器、语法分析器、基本解释器 |
| 1月31日 | 51% | String.prototype 接线错误修复暴露了约 11000 个测试用例(一天内增加了 23%) |
| 2月3日 | 63% | 基于状态机方法的生成器 |
| 2月10日 | 86% | 临时 API 第一阶段 |
| 2月13日 | 88% | 全面扩展:新增 intl402 测试,测试数量几乎翻了一番(约 48k → 约 92k),通过率仍然上升 |
| 2月22日 | 95% | SharedArrayBuffer + Atomics(一天内新增 868 次传递) |
| 3月5日 | 99.6% | 大规模解析器早期错误批处理(274 个修复) |
| 3月9日 | 100% | Array.fromAsync这是最终的解决方案 |
1 月 31 日的更新是我最喜欢的。仅仅修复了一个String.prototype连接方式上的小 bug,就让大约 11,000 个测试一夜之间恢复正常。这正是拥有完整的 test262 测试套件作为反馈信号的价值所在;你可以通过 bug 的影响范围来发现它们。
发动机对比
为了进行比较,我对 Node.js 和 Boa(另一个基于 Rust 的 JS 引擎)运行了相同的 test262 测试套件。相同的代码检出、相同的测试环境、相同的超时时间、相同的机器(128 核):
| 引擎 | 版本 | 场景 | 经过 | 失败 | 速度 |
|---|---|---|---|---|---|
| JSSE | 最新的 | 101,234 | 101,044 | 190 | 99.81% |
| 浮标 | v0.21 | 91,986 | 83,260 | 8,726 | 90.51% |
| 节点 | v25.8.0 | 91,986 | 79,201 | 11,986 | 86.86% |
一些重要的注意事项:Node 的失败案例主要由 Temporal 库导致(Node 25 中未包含该库,其 11,986 个失败案例中有 8,980 个与此相关)。由于测试适配器无法运行 ES 模块,因此 Node 的模块测试(799 个场景)被跳过。Boa 的主要缺陷在于解析器层面(赋值目标验证、类解构、正则表达式属性转义)。这些都是成熟的、生产级别的引擎,而此次比较所依据的指标并非它们专门优化的目标,因此请理性看待此次比较结果。
JSSE 的场景数量更高(101,234 对比 91,986),因为它包含了完整的预发布测试套件。对于非预发布测试,JSSE 的场景数量始终为 100%。
我还针对acorn测试套件运行了 JSSE,作为 test262 之外的实际健全性检查。所有 13,507 个 acorn 测试均通过。
关于分阶段测试的说明
我一度以为 JSSE 也通过了预发布测试。但run-test262.py实际上并没有,它根本没运行这些测试。当我最终显式运行预发布测试套件时,事情变得有趣起来。
非测试环境的测试套件维护良好,内部一致性强。测试环境是测试用例在正式发布前进行测试的地方,这一点也体现libm在测试套件本身。JSSE 目前通过了 2808 个测试场景中的 2762 个(98.36%)。剩余的失败案例分为三类:不稳定的超时、精度差距,以及最值得关注的,测试套件本身似乎存在的 bug。
例如,六个预发布测试使用了一个共享的框架函数,该函数依赖于eval声明的变量泄漏到封闭作用域,而严格模式明确禁止这种泄漏。这些测试在 JSSE 和 Node 上都失败了。它们需要noStrict在上游添加一个标志。另一个例子:两个预发布测试期望块级作用域function arguments()声明具有 AnnexB 提升行为,这与主测试套件中的测试直接冲突。主测试套件反映了最近规范的更改,但目前还没有主流引擎实现该更改。JSSE 遵循规范,因此主测试套件中的测试通过,而预发布测试失败。
当你的目标测试套件开始反击你时,这是一个好兆头。
性能如何?
很糟糕。我知道这是故意的。完全没有进行任何优化。JSSE 是一个纯粹的树遍历解释器,没有字节码编译。以下是一些针对 Node (V8) 和 Boa 的微基准测试:
| 基准 | Node v18 | 浮标 v0.21 | JSSE v0.1 | JSSE 与 Node |
|---|---|---|---|---|
| 环形 | 0.18秒 | 2.02秒 | 2.90秒 | 16倍 |
| 纤维 | 0.21秒 | 2.53秒 | 28.57秒 | 136倍 |
| 细绳 | 0.19秒 | 0.62秒 | 4.74秒 | 25倍 |
| 大批 | 0.17秒 | 0.53秒 | 119.45秒 | 703x |
| 目的 | 0.35秒 | 0.69秒 | 0.85秒 | 2.4倍 |
| 正则表达式 | 0.16秒 | 0.24秒 | 5.62秒 | 35倍 |
| 关闭 | 0.18秒 | 2.79秒 | 15.48秒 | 86倍 |
| JSON | 0.20秒 | 0.44秒 | 0.25秒 | 1.2倍 |
性能差距非常大:从 1.2 倍(JSON,JSSE 实际上优于 Boa)到 703 倍(数组操作)。fib基准测试采用递归斐波那契数列,这会大幅增加函数调用开销,而 136 倍的性能差距正是预期中每次调用都会重新遍历抽象语法树 (AST) 的树遍历器的性能表现。数组基准测试是最坏情况,很可能是数组原型实现中存在某种病态路径。
与 Boa 进行比较更公平,因为它们都是 Rust 解释器。JSSE 的速度大约比 Boa 慢 1.2 倍到 225 倍,具体倍数取决于基准测试。但在对象操作和 JSON 处理方面,JSSE 与 Boa 的性能相当。
test262 测试套件本身就揭示了瓶颈所在。所有 30 个最慢的测试都是正则表达式 Unicode 属性转义测试(\p{...}),每个测试耗时 64 到 84 秒。其中最慢的Script_Extensions_-_Sharada.js测试耗时 84 秒。这些测试编译并运行正则表达式,枚举数千个 Unicode 代码点,瓶颈在于对 Unicode 属性类进行字节级 WTF-8 正则表达式匹配。在总共 198,258 个测试场景中,有 900 个耗时超过 10 秒,1,062 个耗时超过 1 秒。其余测试都很快完成。耗时较长的测试几乎全部是正则表达式测试。
这样很好。正确性是唯一目标。性能提升显然是下一步要做的事情。
成本
我在这个项目中使用了 Claude Code Max 20x 订阅,所以无需按令牌付费。但ccusage它追踪了等效 API 的成本,这个数字很有意思:47 天内 302 次会话,总成本为4,618.94 美元。按模型细分如下:
| 模型 | 成本(API等效) | 分享 |
|---|---|---|
| 克劳德作品 4.6 | 4,062.91 美元 | 88% |
| 克劳德十四行诗 4.6 | 345.54美元 | 7.5% |
| 克劳德·海库 4.5 | 124.83美元 | 2.7% |
| 克劳德·索内特 4.5 | 45.56美元 | 1% |
| 克劳德作品 4.5 | 40.11美元 | 0.9% |
Opus 4.6 承担了绝大部分繁重的工作。Haiku 则用于处理诸如子代理搜索之类的后台任务。总令牌数约为 89 亿,但积极的提示缓存机制有效控制了成本;仅缓存读取的令牌数就达到了 88 亿。
换个角度来看:一个 17 万行的 Rust 代码库,如果 100% 通过 test262 测试,其 API 等效成本为 4,619 美元。这大约是每行代码 0.03 美元,或者每达到 test262 合规性一个百分点,成本约为 47 美元。
我学到了什么
计划比代码更重要。Claude根据规范和 test262 子模块自行生成了整个计划PLAN.md。我没有编写它,也没有审查它,只是接受了它。而且它奏效了。但现在回想起来,计划的质量是项目速度的最关键因素。Claude 选择了一个合理的特性顺序,但它早期的一些架构决策(例如如何处理生成器、如何构建垃圾回收器、如何处理 WTF-8 字符串)导致了后期代价高昂的返工。如果我事先投入时间研究合适的架构并将其融入计划中,我很有信心这个项目只需一半的时间就能完成。经验教训不是“你必须自己编写计划”,而是“无论以何种方式制定,都要确保计划本身是好的”。
test262 是一个绝佳的反馈信号。 拥有一个全面且结构良好的测试套件,并让智能体能够自主运行,正是这类项目得以实现的关键。智能体无需深入理解 JavaScript 语义;它只需要让数值上升,同时保持数值不变即可。test262 正好提供了这样的信号。
代理程序容易在冗长的上下文中迷失方向。 这需要一些解释。Claude Code 会在接近上下文限制(本项目开发时为 20 万个 token)时自动压缩对话。压缩会概括较早的消息以释放空间,但不可避免地会丢失细节:哪些具体的测试失败了、尝试了什么方法、错误信息是什么等等。在一个长时间运行的任务中,经过几轮压缩后,Claude 的性能会明显下降。它会陷入循环,反复尝试相同的修复。它会通过一些测试,但会导致另一些测试出现回归,然后尝试修复这些回归,结果又会破坏其他东西。我的解决方法是停止会话,将任务拆分成更小的部分,然后重新开始。一个带有明确提示(“实现 SharedArrayBuffer”)的新会话比一个经历了多次压缩的长时间循环要好得多。最佳方案是:每个会话处理一个功能,规划它,实现它,运行测试,然后停止。
架构重构成本高昂,但并非灾难性的。 克劳德曾意识到,原有的架构并不适合异步函数,不得不回头重新构建。虽然耗时比预期更长,但最终奏效了。如果架构师是一位真正理解问题所在的人,就能避免这种情况,这也与第一点不谋而合。
Rust 非常适合智能体编程。 我特意选择 Rust,是因为我相信它是目前智能体驱动开发的最佳语言。它的类型系统和编译器可以在运行时之前捕获大量 bug,这意味着智能体可以减少调试时间,将更多时间用于推进开发。严格编译器本质上是继 test262 之后的第二个反馈信号。
接下来会发生什么?
性能方面,引擎本质上是一个树遍历器,最容易实现的改进是字节码编译和简单的虚拟机。仅仅消除重复的抽象语法树遍历,性能提升可能就达到 10 到 100 倍。除此之外,还可以考虑内联缓存、隐藏类,如果想更进一步,最终还要实现 JIT 编译。
但说实话,JSSE 的初衷从来就不是为了构建一个生产级的 JavaScript 引擎。它的目的是探索现有代理编码工具的潜力,并在过程中了解代理的工作流程。就这两方面而言,我对结果都很满意。
结论
这些工具令人惊叹,模型也令人难以置信,但这仅仅是个开始。智能体编码已经开始改变人们的工作方式,我相信它将彻底革新软件生产(我曾就此写过一些想法)。在不久的将来,从零开始编写一个 JavaScript 引擎并将其优化到比其他任何引擎都快,简直易如反掌,真的就像在公园散步一样轻松。我从这次实验中学到了很多,并且会继续乐在其中。
代码位于github.com/pmatos/jsse,采用 MIT 许可证。如果您想自行运行 test262,README 文件中包含完整的复现步骤。欢迎为 JSSE 做出贡献,但请保持贡献的独立性。
© 2026 保罗·马托斯