Skip to content
pmatosPublic

About

An agent-coded JS engine in Rust. I didn't touch a single line of code here. Not one. This repo is a write-only data store. I didn't even create this repo by hand -- my agent did that.

Topics

Resources

Stars

73 stars

Watchers

0 watching

Forks

Latest commit

 

History

1,853 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

jsse

CodSpeed

An agent-coded JS engine in Rust. I didn't touch a single line of code here. Not one. This repo is a write-only data store. I didn't even create this repo by hand -- my agent did that.

Goal: 100% test262 pass rate.

Read more: jsse - a JavaScript engine built by an agent.

Per the test262 specification (INTERPRETING.md), test files without noStrict, onlyStrict, module, or raw flags must be run twice: once in default (sloppy) mode and once with "use strict"; prepended. Our test runner implements this dual-mode execution. Run uv run python scripts/run-test262.py for the current pass rate.

ES Modules now supported with dynamic import() and import.meta. Async tests run with Promise/async-await support.

Structure

  • spec/ — ECMAScript specification (submodule from tc39/ecma262)
  • test262/ — Official test suite (submodule from tc39/test262)
  • tests/ — Additional custom tests
  • scripts/ — Test runner and tooling

Supported Features

  • CLI with file execution, --eval/-e inline evaluation, and REPL mode
  • --version and --help flags
  • Exit codes: 0 (success), 1 (runtime error), 2 (syntax error)
  • Lexer: all ES2024 tokens, keywords, numeric/string/template literals, Unicode identifiers
  • Parser: recursive descent, all statements, expressions, destructuring, arrow functions, classes, private fields, strict mode
  • Interpreter: tree-walking execution with environment chain scoping
    • Variable declarations (var, let, const with TDZ, using, await using)
    • Explicit Resource Management (using/await using declarations, DisposableStack, AsyncDisposableStack, SuppressedError, Symbol.dispose, Symbol.asyncDispose)
    • Control flow (if, while, do-while, for, for-in, for-of, for-await-of, switch, try/catch/finally)
    • Functions (declarations, expressions, arrows, closures)
    • Classes (declarations, expressions, inheritance, super, static methods/properties, private fields, private methods, private accessors, #x in obj brand checks)
    • Operators (arithmetic, comparison, bitwise, logical, assignment, update, typeof, void)
    • Objects and arrays (literals, member access, computed properties)
    • Template literals (including tagged templates)
    • String.raw
    • new operator with prototype chain setup
    • new.target meta-property
    • this binding (method calls, constructors, arrow lexical scoping)
    • Property descriptors (data properties with writable/enumerable/configurable)
    • Prototype chain inheritance (Object.prototype on all objects)
    • Getter/setter support (object literals, classes, Object.defineProperty)
    • Object.defineProperty, Object.getOwnPropertyDescriptor, Object.getOwnPropertyDescriptors, Object.defineProperties, Object.keys, Object.freeze, Object.getPrototypeOf, Object.setPrototypeOf, Object.create, Object.entries, Object.values, Object.assign, Object.is, Object.getOwnPropertyNames, Object.getOwnPropertySymbols, Object.groupBy, Object.preventExtensions, Object.isExtensible, Object.isFrozen, Object.isSealed, Object.seal, Object.hasOwn, Object.fromEntries
    • Function.prototype.call, Function.prototype.apply, Function.prototype.bind
    • Object.prototype.hasOwnProperty, Object.prototype.toString, Object.prototype.valueOf, Object.prototype.propertyIsEnumerable, Object.prototype.isPrototypeOf, Object.prototype.__defineGetter__, Object.prototype.__defineSetter__, Object.prototype.__lookupGetter__, Object.prototype.__lookupSetter__
    • Number/Boolean primitive method calls (toString, valueOf, toFixed)
    • instanceof and in operators
    • ToPrimitive with valueOf/toString coercion for objects
    • Wrapper objects (new Boolean, new Number, new String) with primitive value
    • eval() support
    • Function, GeneratorFunction, AsyncFunction, AsyncGeneratorFunction constructors (dynamic function creation)
    • Symbol with well-known symbols (iterator, hasInstance, toPrimitive, etc.)
    • delete operator for object properties
    • Iterator protocol (Symbol.iterator, lazy ArrayIterator, StringIterator)
    • Array.prototype.values(), .keys(), .entries(), [@@iterator]() returning lazy iterators
    • String.prototype[@@iterator]() with Unicode code point iteration
    • Spread elements in arrays and function calls (iterator-protocol aware)
    • Rest parameters in function declarations
    • Destructuring (array and object patterns, iterator-protocol aware)
    • RegExp literals and RegExp constructor with test, exec, toString
    • Array prototype methods (push, pop, shift, unshift, indexOf, lastIndexOf, includes, join, toString, concat, slice, splice, reverse, fill, forEach, map, filter, reduce, reduceRight, some, every, find, findIndex, findLast, findLastIndex, sort, flat, flatMap, at)
    • Array.isArray, Array.from, Array.of
    • String prototype methods (charAt, charCodeAt, indexOf, lastIndexOf, includes, startsWith, endsWith, slice, substring, toLowerCase, toUpperCase, trim, trimStart, trimEnd, repeat, padStart, padEnd, concat, split, replace, replaceAll, at, search, match)
    • Proper Error objects (TypeError, ReferenceError, SyntaxError, RangeError) with prototype chains and instanceof support
    • JSON.stringify (replacer function/array, space/indent, toJSON, circular detection, BigInt TypeError, wrapper unwrapping), JSON.parse (reviver), JSON.rawJSON, JSON.isRawJSON
    • String.fromCharCode
    • Map built-in (constructor, get, set, has, delete, clear, size, entries, keys, values, forEach, @@iterator, Map.groupBy)
    • Set built-in (constructor, add, has, delete, clear, size, entries, keys, values, forEach, @@iterator, ES2025 set methods: union, intersection, difference, symmetricDifference, isSubsetOf, isSupersetOf, isDisjointFrom)
    • Generator functions (function*, yield, yield*) with replay-based execution
    • Generator prototype (next, return, throw, Symbol.iterator, Symbol.toStringTag)
    • ES Modules (import, export, import(), import.meta, top-level await)
    • ArrayBuffer (constructor, byteLength, slice, isView)
    • TypedArrays (Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array, BigInt64Array, BigUint64Array) with indexed access, prototype methods (at, set, subarray, slice, copyWithin, fill, indexOf, lastIndexOf, includes, find, findIndex, findLast, findLastIndex, forEach, map, filter, reduce, reduceRight, every, some, reverse, sort, join, toString, toReversed, toSorted, entries, keys, values, from, of)
    • DataView (constructor, all get/set methods for Int8 through BigUint64, little/big endian)
    • Temporal (all types: Instant, ZonedDateTime, PlainDateTime, PlainDate, PlainTime, PlainYearMonth, PlainMonthDay, Duration, Temporal.Now; full arithmetic, comparison, rounding, formatting; IANA timezone support via ICU4X; 12 calendar systems; 100% test262 + intl402/Temporal compliance)
    • Intl.DateTimeFormat (partial — dateStyle/timeStyle, calendar-aware Temporal formatting)
    • Intl.NumberFormat (partial)
    • ShadowRealm (constructor, evaluate, importValue; full cross-realm value wrapping with WrappedFunction exotic objects)
    • globalThis
    • Built-ins: console.log, console.assert, Error, Test262Error, $DONOTEVALUATE$

Building & Running

cargo build --release
./target/release/jsse <file.js>
./target/release/jsse -e "1 + 1"
./target/release/jsse              # starts REPL

Development checks

Install and verify the pinned repository hooks and their environments:

uv tool install pre-commit
./scripts/ensure-hooks.sh

Provision agent workspaces with this command before mounting Git metadata read-only, and provide a writable PRE_COMMIT_HOME cache. Launch agents through ./scripts/agent-session.sh --worktree <path> <agent-command> [arguments...] to install hooks in the selected worktree and start the agent there only after setup succeeds. Omit --worktree to use the current worktree. For example:

git worktree add -b feat/example ../jsse-example
./scripts/agent-session.sh --worktree ../jsse-example <agent-command>

Claude Code also runs setup on SessionStart. Its PreToolUse gate blocks edits and shell commands when hooks are missing, so a failed startup installation cannot silently lead to implementation work. Setup preserves existing Git hooks and verifies both pre-commit and commit-msg. Agents must not commit if setup fails or bypass checks with --no-verify, SKIP, or an overridden core.hooksPath.

The token-limit hook checks only added or modified staged files, including rename destinations, using their staged contents. The limit is 5,000 tokens, estimated as bytes/4 (20,000 bytes). A file that is already over the limit may be edited as long as it does not grow (a ratchet against its size at the base commit), so large files shrink as code is extracted from them; new files and files under the limit must stay at or below it. Unstaged files, deletions, symlinks, submodules, and binary assets are skipped. Lockfiles, generated Unicode/emoji tables, test262 baselines, the changelog, and benchmark results are exempt via token-limit-ignore in .gitattributes. Add specific generated or vendored paths there when needed; authored files must be split into cohesive modules, test files, or linked documents. The hook reports each oversized file and provides splitting and re-staging instructions.

Run just this check (always staged-only, even with --all-files):

pre-commit run staged-file-token-limit

The existing required CI build job also checks committed PR changes, reading blobs and exemptions from the head commit rather than an empty CI index:

uv run python scripts/check_staged_file_tokens.py --base origin/main --head HEAD

Run every hook against the full repository when changing hook configuration or tooling:

pre-commit run --all-files

Running test262

cargo build --release
uv run python scripts/run-test262.py

Options: -j <n> for parallelism (default: nproc/2), --timeout <s> (default: 120). The runner exits non-zero for baseline regressions. Use --fail-on-failures when a selected test set must be 100% passing, as CI does for smoke/sample runs.

The runner supports multiple engines via --engine:

uv run python scripts/run-test262.py --engine jsse      # default
uv run python scripts/run-test262.py --engine node --binary /path/to/node
uv run python scripts/run-test262.py --engine boa --binary /path/to/boa

Per-test timing data is written to /tmp/timing-{engine}.json after each run.

Fuzzing

Seven cargo-fuzz targets live under fuzz/: parse (the parser must never panic on arbitrary bytes), execute (scripts run in-process under a step budget on the bytecode VM and the tree-walker; a panic or a difference between the two is a finding), typed_array (structured sequences of resizable/shared buffer, view, detach, transfer and Atomics operations, run the same way; see docs/adr/2026-10-23-1600-typed-array-fuzz-target.md), execute_ast (the same check, but the input is a JS AST decoded with arbitrary and printed to source, so almost every input parses), gc_stress (allocation-heavy scripts run with and without forced GC at every Nth safepoint, the period taken from the first input byte; any panic or observable difference is a finding, see docs/adr/2026-10-23-1800-gc-stress-fuzz-target.md), reentrancy (fuzzer bytes decode into a plan that renders to a script whose Proxy traps, generators, accessors and callbacks call back into the engine mid-operation, judged by the same oracle; see docs/adr/2026-10-23-1600-reentrancy-fuzz-target.md) and differential (jsse vs node on the same source, surfacing divergences no fixed corpus would). See docs/adr/0004-fuzz-lib-target-and-subprocess-differential.md for the design and .claude/skills/fuzzing/SKILL.md for the full run/triage workflow.

cargo install cargo-fuzz --locked
cargo +nightly fuzz run parse -- -max_total_time=60
cargo +nightly fuzz run execute -- -max_total_time=60 -max_len=16384
cargo +nightly fuzz run typed_array -- -max_total_time=60 -max_len=768
cargo +nightly fuzz run execute_ast -- -max_total_time=60 -max_len=4096
cargo +nightly fuzz run gc_stress -- -max_total_time=60 -max_len=4096
cargo +nightly fuzz run reentrancy -- -max_total_time=60 -max_len=32
cargo build --release   # differential needs the release binary
cargo +nightly fuzz run differential -- -max_total_time=60 -timeout=30

Where each target runs in CI:

Target Workflow Sanitizer Cadence
parse clusterfuzzlite-pr.yml (ClusterFuzzLite, code-change mode, 120 s fuzz time, blocking) ASan every PR
parse clusterfuzzlite-batch.yml (ClusterFuzzLite, batch mode, 30 min) ASan daily
execute clusterfuzzlite-pr.yml (same job as parse; the 120 s is shared across all ClusterFuzzLite targets) ASan every PR
execute clusterfuzzlite-batch.yml (same job as parse) ASan daily
typed_array clusterfuzzlite-pr.yml (same job as parse; the 120 s is shared across all ClusterFuzzLite targets) ASan every PR
typed_array clusterfuzzlite-batch.yml (same job as parse) ASan daily
execute_ast clusterfuzzlite-pr.yml (same job as parse; the 120 s is shared across all ClusterFuzzLite targets) ASan every PR
execute_ast clusterfuzzlite-batch.yml (same job as parse) ASan daily
gc_stress clusterfuzzlite-pr.yml (same job as parse) ASan every PR
gc_stress clusterfuzzlite-batch.yml (same job as parse) ASan daily
reentrancy clusterfuzzlite-pr.yml (same job as parse; the 120 s is shared across all ClusterFuzzLite targets) ASan every PR
reentrancy clusterfuzzlite-batch.yml (same job as parse) ASan daily
differential fuzz.yml (needs node and a release jsse, so it stays outside ClusterFuzzLite) none nightly

clusterfuzzlite-build.yml uploads an ASan build of main on every push so a PR run can tell a crash the PR introduced from one already on main. The corpus found by batch runs is stored as a GitHub Actions artifact; the next batch run and every PR run start from it (plus the committed seeds in fuzz/corpus/parse/, fuzz/corpus/execute/, fuzz/corpus/typed_array/, fuzz/corpus/execute_ast/, fuzz/corpus/gc_stress/ and fuzz/corpus/reentrancy/). The artifact expires with the repository's artifact retention, after which fuzzing restarts from the committed seeds.

To triage a ClusterFuzzLite failure, open the failed run, download its clusterfuzzlite-*-crashes artifact (or copy the testcase from the log) and reproduce with cargo +nightly fuzz run --debug-assertions <target> <testcase>; see .claude/skills/fuzzing/SKILL.md. A finding from any ClusterFuzzLite target is a lead, not a fix to make in the PR that touches the fuzz tooling itself: file it as its own issue and fix it against spec//test262, never by matching node's observed behavior. A PR check can also fail on a latent bug the PR did not touch; file it and re-run.

Running JetStream 3

gh repo clone WebKit/JetStream /tmp/JetStream -- --depth 1
cargo build --release
uv run python scripts/run-jetstream.py --test OfflineAssembler --iterations 1 --timeout 120

The runner covers pure-JS JetStream 3 workloads and skips Wasm/Worker-dependent tests. Each workload is measured three times by default. The reported score is derived from the median of the measured times, and the min-max range of the repeats is printed alongside it; a range above 5% is flagged UNSTABLE. Controlled runs refuse a one-minute load average above 1.5 and use the maximum-frequency CPU cluster when Linux cpufreq data and taskset are available. Use --repeats and --idle-threshold to tune the protocol; --no-idle-gate is intended only for diagnostic or parallel runs. JSON output includes the repeat samples and a host/load/topology fingerprint.

The runner drives workloads with its own harness rather than JetStream's cli.js, so it emulates the few shell globals they rely on: self (=== globalThis) and a JetStream object with preload paths and an async getString backed by the embedded preload files (getBinary rejects as unsupported). These live only in the generated script, never in jsse's own globals. An async workload that rejects is reported as benchmark threw: ..., and every failed workload keeps its clipped stdout/stderr in the --json report and the first line of it on the FAIL line.

A refused run exits 3 (distinct from argparse's 2) and leaves any existing jetstream-results.json untouched, so a truncated suite cannot overwrite a complete baseline. This protection also applies when --json names that file through an equivalent absolute or symlinked path; choose a distinct output path to retain partial refusal evidence. Because a single-threaded benchmark contributes roughly 1.0 to the one-minute average itself, the default 1.5 threshold leaves only about 0.5 of headroom for foreign load; raise --idle-threshold on a host with unavoidable background activity rather than disabling the gate.

To confirm the gate actually fires, run the suite against a synthetic load. One yes asymptotes the one-minute average to only ~1.0, which is below the 1.5 default, so use enough burners to clear the threshold:

for _ in 1 2 3; do yes > /dev/null & done
sleep 60   # let the 1-minute average converge
uv run python scripts/run-jetstream.py --test Air --iterations 1
# expect: "BUSY  Air  (loadavg1 ... exceeds idle threshold 1.50)" and exit 3
kill %1 %2 %3

Running the Node-compat library tests

Beyond test262, real-world npm libraries are run as engine stress tests: each library's own upstream test suite is bundled with esbuild and executed on target/release/jsse, cross-checked against Node as a reference oracle.

cargo build --release
./scripts/run-library-tests.sh <lib>          # e.g. decimal.js, acorn, zod, moment
./scripts/run-library-tests.sh <lib> --clean  # force a fresh clone/rebuild
./scripts/run-library-tests.sh <lib> --node   # run on Node only (reference)

<lib> is any config name under scripts/libs/: acorn, ajv, big.js, bignumber.js, decimal.js, highlight.js, js-md5, js-sha256, lodash, luxon, moment, prismjs, qs, uglify-js, zod. None of this is baked into jsse's globals or run in CI — it's a manual, additive-only harness that never affects test262. See scripts/README.md for the full recipe, the current per-library status table, and how to add a library.

About

An agent-coded JS engine in Rust. I didn't touch a single line of code here. Not one. This repo is a write-only data store. I didn't even create this repo by hand -- my agent did that.

Topics

Resources

Stars

73 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages