This runbook defines the expected operating model for autonomous agents working on this repository.
Known structural risks in this operating model are recorded in
docs/harness-convergence-risks.md.
The repository's default ECMAScript conformance baseline is the latest
ratified standard, ECMA-262 16th edition, June 2025 (ECMAScript 2025 / ES2025).
Use the tc39/ecma262@es2025 specification tag as the normative text anchor.
Test262 has no edition-specific stable tag, so use the pinned
third_party/test262 commit for executable coverage and the pinned QuickJS-NG
build for differential behavior. TC39 living-draft and Stage 3+ Test262 cases
remain valid campaign inputs, but do not redefine the default baseline until
the corresponding ECMAScript edition is ratified and this document is updated.
main is the stable integration branch. Before starting parallel work:
git switch main
git pull --ff-only
./scripts/bootstrap.sh
./scripts/check.sh
git rev-parse HEADRecord the resulting commit as the task base sha.
Use a normal feature branch when only one agent is editing:
git switch -c agent/<task-slug>/<owner-id>
./scripts/bootstrap.shKeep the commit focused on one reviewable unit. Run the task-specific checks and
then ./scripts/check.sh.
Push the branch after it has a locally verified commit when early GitHub Actions signal is useful:
git push -u origin agent/<task-slug>/<owner-id>
gh run list --branch agent/<task-slug>/<owner-id> --limit 1
gh run watch <run-id> --exit-statusRemote CI is an additional signal, not a replacement for local verification. Do not merge or stack dependent work on a branch whose latest pushed CI run is red or still unexplained.
For changes that can affect runtime scheduling, CI scripts, or hosted-runner resource usage, run the GitHub Actions check job locally through the parity wrapper:
./scripts/check-ci.shThis uses serialized split runtime tests and skips the Test262 subset because
CI runs that subset in a separate job. scripts/check.sh prints coarse stage
timings in both normal and split modes so a canceled remote step leaves enough
context to distinguish compile, lint, runtime, file-size, and subset failures.
For AI iteration and pre-commit validation, use the touched-file gate:
./scripts/check-touched.sh --staged --explainThe touched gate keeps commits fast by selecting crate tests and focused
Test262 allowlist prefixes from changed paths. Use --base <ref> --explain
when validating an accumulated branch slice. It is an early warning gate, not a
replacement for ./scripts/check.sh before final handoff or push.
Use isolated worktrees only when ownership boundaries are clear:
./scripts/create-agent-worktree.sh <task-slug> <owner-id> <base-sha>Each owner must receive:
- task id and goal;
- base sha;
- branch name;
- worktree path;
- allowed paths;
- forbidden paths;
- verification command.
When an owner produces a locally verified commit, they may push their feature
branch immediately to trigger branch CI while other owners continue in separate
worktrees. The main agent should poll or watch those runs with gh, record any
failed run URL in the handoff, and route fixes back to the same owner branch.
Global files stay main-agent owned unless explicitly assigned:
Cargo.tomlCargo.lockrust-toolchain.toml.gitmodulesAGENTS.mdREADME.mddocs/architecture.md- shared scripts and CI files
Each coding owner must report:
- branch name;
- tip commit sha;
- base sha used;
- changed files;
- verification run;
- pushed CI run URL and status, when the branch was pushed;
- residual risks.
The main agent integrates one branch at a time.
Before merging, validate branch scope:
./scripts/validate-agent-branch.sh <branch> <base-sha> <allowed-path>...Then inspect and integrate:
git diff --stat <base-sha>..<branch>
git merge --no-ff <branch>
./scripts/check.shRun additional checks when relevant:
./scripts/compare-qjs.sh
./scripts/find-qjsng-gaps.sh --filter test/built-ins/String --limit 100
./scripts/test262-subset.sh
./scripts/microbench.shFor performance changes, scripts/microbench.sh is only a fast diagnostic.
Use the versioned black-box runner for reviewable evidence, with engine builds
kept outside the measurement process:
cargo build --release -p qjs-cli
./scripts/benchmark.sh --candidate target/release/qjs --blocks 1 \
--case plain_function_call \
--output target/benchmarks/single-role-smoke.jsonl
./scripts/lifecycle-bench.sh --quickThe black-box command is a local single-role smoke only; it cannot be passed to the three-role report tool. Report-grade evidence requires the full frozen portfolio, all three binaries, clean recipe-matching receipts, and an explicit new output path:
./scripts/benchmark.sh \
--candidate /path/to/candidate/qjs \
--candidate-receipt /path/to/candidate-receipt.json \
--base /path/to/base/qjs \
--base-receipt /path/to/base-receipt.json \
--quickjs-ng /path/to/quickjs-ng/qjs \
--quickjs-ng-receipt /path/to/quickjs-ng-receipt.json \
--blocks 30 --seed 20250713 \
--output target/benchmarks/report-grade-run.jsonl
./scripts/benchmark-report.sh --analysis-manifest benchmarks/analysis.json \
--input target/benchmarks/report-grade-run.jsonl \
--output target/benchmarks/report-grade-report.jsonThe production experiment contract, profile/series identity, QuickJS-NG
comparison ordering, and external-corpus admission rules are in
docs/benchmarking.md.
scripts/external-corpus-audit.sh validates the deny-only external-corpus v1
registry without fetching or executing anything. --registry <path> performs
structural validation of custom input and cannot be combined with
--require-admitted <id>. The latter consults only the checked-in trust root
and always fails because v1 cannot represent admission. The registry has five
blocked source-pinned candidates and two excluded evidence-backed decisions;
it is governance metadata, not a benchmark result or performance claim. Real
admission requires a separately reviewed, content-hashed v2 audit bundle.
scripts/performance-policy-audit.sh validates the checked-in fail-closed CI
policy and cross-checks the four current benchmark protocol hashes, direct
QuickJS-NG pin, full control/audit-chain aggregate hash, and external registry
state. --require-gate nightly|release|pr_sentinel always
fails in v2. --policy <path> only validates custom structure and is mutually
exclusive with gate requirements. The hosted
.github/workflows/performance-smoke.yml runs performance-preview.sh for
explicit candidate/base SHAs and pinned QuickJS-NG. Its pull_request_target
path uses the base-owned workflow, setup action, and harness for
same-repository PRs targeting main; fork preview is unsupported. Its push
path runs for every main update, including merges and direct pushes, using
github.event.after as the head-owned harness/candidate and
github.event.before as the base. Push admission rejects the wrong event, ref,
repository, malformed or zero SHAs, or an after SHA different from
github.sha. A manual main dispatch can additionally supply base_sha to
compare the selected current main candidate against one explicit fixed
base. The base must be a full non-zero SHA from the same repository and an
ancestor of that candidate; the workflow fetches enough history to verify the
relationship from the full candidate checkout before measurement, while the
separate base checkout stays pinned to the exact build source. Omitting
base_sha preserves the same-revision integrity smoke. The current main
checkout still owns the
harness in both manual forms. Both paths use read-only permissions without
secrets. This is a cooperative integrity scope, not a malicious candidate
sandbox. It publishes ratios only after a strict three-block, 25-case
non-claim health check. Pending or failed runs still publish a status summary
and any available provenance, but no ratio conclusion. Phase status identifies
the failed build/measurement/summary stage, and even pre-orchestrator failure
creates Markdown and JSON evidence. There is no threshold or gate.
The main-push path first runs reference-engine-cache, which restores the
exact pinned QuickJS-NG executable or builds and saves it before measurement.
The dependent preview job uses always(), so failed cache preparation
degrades to the orchestrator's normal rebuild path rather than skipping that
main update. The preparation job never runs a benchmark and never caches
evidence; all 25 cases are measured again on every push.
The hosted workflow restores exact-key, content-addressed final engine
executables to avoid recompiling unchanged Rust inputs and the fixed QuickJS-NG
revision. Candidate/base share the Rust content namespace. Keys cover tracked
Cargo/workspace/crate inputs, hosted image, OS release/kernel/libc, targets,
actual compiler/linker paths, identities and digests, all effective
build-affecting environment, and exact recipes; QuickJS-NG also binds its pin.
Every entry is revalidated against metadata, executable mode,
size, and SHA-256 or rebuilt. Cache misses revalidate clean source immediately
after compilation and before atomic storage. PR-target runs restore only; trusted main pushes
pre-save the pinned reference before measurement, then independently
revalidate and can save fallback entries even after a later measurement/report
failure. Cache-service errors degrade to rebuild/no-save.
Measurements, receipts, reports,
summaries, and artifacts always regenerate, with provenance in
build-cache.json.
Fixed-hardware qualification, A/A calibration, a noise envelope, and the PR false-positive budget remain future reviewed work.
The lifecycle command is a Rust-native parser/compiler diagnostic. Its normal
mode uses the frozen Criterion configuration and writes standard artifacts
under target/criterion; --quick is only a smoke, forced to
target/criterion-smoke with baseline output discarded. The wrapper rejects
all options except positional filters, the documented exact long display/run
flags, exact -v/-n/-h, and equals-only --color/--format values. It
never runs as part of check.sh, is not compared with QuickJS-NG, and is not a
CI threshold. Fixture v1 length and FNV-1a drift sentinels fail closed; content
changes require a v2 ID. Output destruction is deferred outside timing. The
realm-construction boundary is absent until the engine naturally exposes a
production public API.
scripts/find-qjsng-gaps.sh is the first-choice entrypoint for discovering
behavior supported by the pinned QuickJS-NG reference but not yet supported by
quickjs-rust. It runs scripts/test262-baseline.sh --engine both, stores the
raw summary and case results under target/test262-gaps/, and prints a compact
report with the total QuickJS-NG-pass/quickjs-rust actionable gap count, the
split between runtime failures, included timeouts, excluded stress timeouts, and
quickjs-rust not-run cases, top affected areas, and the first cases to
investigate. Use
--filter test/<prefix> to focus the scan on one Test262 subtree and --all
when the focused scan should be exhaustive. It prints a greedy next area by
default. For unfiltered --all recommendation runs, the default is a bounded
greedy probe over TEST262_GAP_PROBE_LIMIT cases, currently 100, from four
shards of 16. When no explicit selection is given via --probe-shards or
TEST262_GAP_PROBE_SHARDS, the default shard set rotates one step per probe
run (state in target/test262-gaps/.probe-rotation), so four consecutive
probes sweep all 16 shards instead of resampling the same files. Those
probe shards run concurrently, and the report merges their case results before
ranking areas. This gives the agent broader Test262 coverage per iteration
without paying for a full audit or biasing entirely toward the first sorted
Test262 directories. After that sampled candidate queue is built, the default
global probe exact-checks the top TEST262_GAP_VERIFY_CANDIDATES areas,
currently 8. These focused verification runs execute concurrently, then the
script prints the final recommendation and a greedy queue from those exact
focused results. To keep the default greedy loop fast, exact verification skips
candidate subtrees with more than TEST262_GAP_VERIFY_AREA_MAX_FILES
JavaScript files, currently 150. Broad skipped areas remain visible in the raw
probe queue, but they do not block the verified recommendation. This costs a
few additional focused baseline runs, prevents a sampled broad area from hiding
smaller parity wins, and gives the agent several ready follow-up areas from one
global probe.
The default recommendation strategy is quickwins greedy. It prefers real
quickjs-rust engine failures when they fit in a small reviewable batch. After
that, it prefers small harness-only batches when at least one case does not
carry a hard-feature hint, because those mixed batches are often faster to
verify or clear than a broad semantic area. Harness-only batches where every
case is hard-hinted remain visible, but they rank below mixed quick wins. It
also computes hard hints from Test262 feature metadata, paths, and skip metadata
that usually imply larger missing features, such as async, destructuring, class,
yield, proxy/realm/species behavior, resizable or growable buffers, Atomics
agent wait/notify coordination, or Annex B global-code semantics. It also
hard-hints known stress-shaped timeout cases, such as large typed-array concat
and full BMP regexp escape sweeps, as performance work. The ranking weights
those hints by expected breadth, so resizable or growable buffers, Atomics
agent coordination, async, stress timeouts, and Annex B global-code work rank
below narrower realm/proxy/species failures when the engine-gap count is
otherwise similar. Those hints do not hide gaps; they only lower an area's
default ranking so an agent can find reviewable parity wins before getting
stuck on known broad features. Hard-hinted areas that still contain executable
engine failures stay ahead of pure $262.agent/harness not-run areas, so the
queue does not recommend host-only support before reachable semantic work. Use
--strategy fast for the older small-batch-first behavior,
--strategy largest to restore largest-gap-first recommendation, or
--recommend-batch-cap N to tune how large a default batch may be. Use
--recommend-queue N or TEST262_GAP_RECOMMEND_QUEUE to tune how many ranked
areas the report prints for continuous or parallel follow-up work. Use
--verify-candidates N to tune the exact candidate follow-up,
--verify-area-max-files N to tune how wide a subtree may be before default
verification skips it, or --verify-candidates 0 when the fastest possible
sampled recommendation is more useful than a verified one.
After a global probe has produced a candidate queue, use
--from-report target/test262-gaps/<run> or --from-latest-report to recompute
the recommendation from the saved cases.jsonl without executing Test262
again. Pair it with --skip-area test/<prefix> when the current top area is
already being worked or has just been rechecked, so the agent can pick the next
largest known gap immediately. This replay mode is only a planning shortcut:
focused verification still needs --filter test/<prefix> --all, and final
completion still needs --exact --all.
Use --exact --all when the task needs a complete report or when a probe finds
no gaps and the agent needs to prove the exit condition. Under the default
quickwins strategy, mixed harness-only areas remain first-class candidates when
they are small enough to check quickly, and broad-feature areas remain visible
in the candidate queue with their hard count. Stress timeouts are excluded
from the default actionable gap list so large conformance stress loops do not
hide missing behavior; use
--include-timeouts when performance parity is the task. Use --probe-limit N
and --probe-shards I/N[,I/N...] to tune recommendation speed versus
confidence; --probe-shard I/N remains as a single-shard shorthand for very
fast local checks. Use --no-recommend when only the raw gap report is needed.
Treat one recommended queue area as the smallest useful planning and commit
boundary unless the area is too broad to review as one change. When several
queue entries touch independent subsystems, agents may work them as separate
reviewable units after focused --filter ... --all verification; avoid
splitting follow-up work into one commit per individual Test262 case.
scripts/test262-subset.sh runs the curated Test262 allowlist. Allowlist
entries may point to local derived cases under tests/test262/cases/ or pinned
upstream cases under third_party/test262/test/. Upstream entries are expanded
into temporary files with Test262 assert.js, sta.js, and metadata
includes before execution. Entries in tests/test262/expected-failures.txt
must also be in the allowlist and must include a reason. Expected-failure cases
may fail without failing the subset run; if one passes, the script fails and
asks for the stale entry to be removed. GitHub CI gives individual subset cases
a wider timeout than the local default to avoid false failures on shared
runners, and scripts/check.sh uses the same 30-second subset timeout unless
TEST262_CASE_TIMEOUT_SECONDS is set explicitly. Direct test262-subset.sh
runs keep the 10-second default for tighter interactive probes. Allowlist
validation and case execution both run in parallel;
TEST262_JOBS overrides the default of one worker per online CPU.
The pinned Test262 revision predates the official immutable-ArrayBuffer
metadata fix in tc39/test262@250f204f23a9249ff204be2baec29600faae7b75.
Both runners source scripts/test262-upstream-amendments.sh to backport exactly
the eleven upstream ["immutable"] TypedArray factory exclusions until the
gitlink advances past that fix. The affected files still execute every mutable
constructor-argument factory and every assertion; no test file is skipped, and
immutable-ArrayBuffer-specific coverage remains unchanged. Remove the backport
when the pinned submodule contains the upstream commit.
scripts/test262-baseline.sh scans upstream Test262 coverage. It can run a
bounded sample, a full scan with --all, a shard with --shard I/N, and a
quickjs-rust/QuickJS-NG comparison with --engine both. In that mode QuickJS-NG
config skips are applied as the shared baseline. The quickjs-rust side reports
not-run cases only for structural harness limits such as unsupported harness
includes, $262.agent/atomicsHelper.js multi-agent coordination, intl402,
and fixtures; there is no source-syntax pre-filter anymore
(the former for-of destructuring and class filters were lifted once those
features landed). Module-flagged cases are no longer structurally skipped: they
run through the qjs --module --prelude <file> channel, where the test file is
evaluated under the Module goal, relative specifiers resolve against the test
file's directory (_FIXTURE.js files are the imported modules and stay
non-executed standalone), and the harness includes are installed as a script
prelude in the module graph's realm. Module resolution-phase negatives surface
as early errors and are matched accordingly. Async-flagged cases are no longer
structurally skipped: the harness includes doneprintHandle.js like
quickjs-ng's runner, the qjs CLI exposes a host print global and drains the
promise job queue after evaluation, and positive async cases are judged by the
Test262:AsyncTestComplete / Test262:AsyncTestFailure markers $DONE prints
(missing marker or a non-zero exit counts as a failure). Async negative cases
stay on the negative-metadata path. Test262
features metadata is parsed for QuickJS-NG config alignment, but it does not
preemptively skip quickjs-rust cases; runnable cases produce normal pass, fail,
or timeout signal. Negative Test262 cases are runnable by the quickjs-rust
baseline harness; parse, early, runtime, and resolution failures are matched
against the Test262 negative metadata before being counted as expected results.
Raw Test262 cases run without injected harness files. --stop-after-limit is
reserved for bounded probe callers such as find-qjsng-gaps.sh; do not use it
for coverage accounting because it stops enumeration once the run limit is
reached. Set QJS_CLI_BIN to reuse a prebuilt quickjs-rust binary across
multiple shard runs, or set QJS_CLI_PROFILE=release to have the harness build
and run an optimized qjs-cli binary for timeout- or performance-sensitive
gap scans. Case enumeration and skip classification stay serial, but
case execution runs through a parallel worker pool sized by
TEST262_BASELINE_JOBS (default: one worker per online CPU); summary counts,
JSONL artifacts, and diagnostic ordering are unchanged, while per-case
progress lines on stdout interleave in completion order. Lower
TEST262_BASELINE_JOBS when several baseline instances share one machine. Set
TEST262_TIMEOUT_RETRIES to rerun only cases that hit the timeout wrapper; the
default is 0, and each retry uses the same per-case timeout so stable
performance gaps remain reported as timeouts.
The quickjs-rust baseline case builder injects Test262 harness files on demand:
assert.js for assert helpers, sta.js when a case, required assertion helper,
or included harness file needs Test262Error/$DONOTEVALUATE, the $262 host
shim for $262, doneprintHandle.js for async completion, plus declared
metadata includes. Include-only stress cases therefore do not pay for the full
assertion, Test262Error, and host prelude unless those helpers are used by the
case or by an included harness file. The assert.sameValue fast path is
installed by assigning assert.sameValue = __quickjsRustAssertSameValue after
assert.js loads; the harness does not rewrite test source identifiers, because
doing so changes observable with/Proxy name resolution.
The cross-realm Array shim forwards through Reflect.construct with the
incoming new.target, so proxy-wrapped constructor targets still exercise
GetFunctionRealm-style fallback behavior.
The
Test262 Coverage GitHub Actions workflow starts for each successful CI
commit, but a newer push to the same branch cancels a superseded in-flight
run, so rapid push sequences settle on the latest commit's scan. It
runs the sharded quickjs-rust scan and QuickJS-NG baseline in parallel,
uploads shard summaries, and aggregates the result into the workflow summary.
The aggregate is a conformance gate: it fails when QuickJS-NG passes a case
that quickjs-rust fails, times out, or cannot run. The burndown and merged
comparison artifacts still upload on that failure so the exact cases remain
diagnosable. CI uploads the checked commit's
optimized qjs-cli binary, and coverage jobs reuse that release artifact
instead of rebuilding the runner binary on every shard group; local
release-profile scans use the same runner profile for diagnosing timeout
credibility and runtime-performance gaps. The
quickjs-rust scan uses 16 coverage groups; each group runs its two Test262
shards sequentially, and each shard saturates the runner's cores through the
baseline script's internal worker pool while keeping the full 32-shard scan
complete. The quickjs-rust coverage shards retry timeout cases once with the
same 10-second case timeout to reduce shared-runner flake without hiding stable
slow cases. The
workflow reuses a full QuickJS-NG baseline cache when available;
when that cache is missing, it falls back to sharded baseline jobs and saves a
full cache for later commits.
scripts/test262-burndown.sh records the conformance burndown time series in
docs/conformance/burndown.jsonl. Use --report DIR with the output of a
complete local scan (sharded test262-baseline.sh --all --engine both runs or
find-qjsng-gaps.sh --exact --all), or --entry FILE with the
test262-burndown artifact that the Test262 Coverage workflow uploads for
each aggregated commit. The script refuses partial or filtered scans so every
entry in the series is comparable. Record an entry after each full exact scan
and when a CI aggregate provides a fresh per-commit measurement; the
comparison.actionable_gap and comparison.ng_pass_rust_not_run trends are
the convergence signal that decides when the recommendation strategy or
campaign priorities should change. See docs/conformance/README.md for the
schema.
The coverage workflow also uploads test262-comparison-cases, a merged
per-case JSONL generated by scripts/test262-aggregate.py. Use that artifact,
not the rust-only test262-coverage-* shard artifacts, when reusing a complete
CI scan to choose follow-up gap areas. Rows include comparison buckets and an
actionable_gap boolean for cases where QuickJS-NG passes while quickjs-rust
fails or times out.
scripts/microbench.sh runs the repository's current QuickJS microbenchmark
subset from tests/benchmarks/quickjs/microbench.js. Use --engine quickjs-ng
or --engine both to compare the same subset against the pinned QuickJS-NG
reference, and pass benchmark name prefixes to narrow a run.
Do not integrate another owner branch until the target branch is green. For pushed owner branches, also check the latest branch CI status before merging:
gh run list --branch <branch> --limit 1
gh run view <run-id> --json status,conclusion,url,jobsAfter a branch is merged and verified:
git worktree remove <worktree-path>
git branch -d <branch>Retain failed branches or worktrees only when they are needed for diagnosis, and record that residual state in the final report.
If validation fails, do not merge. Re-brief the owner with the out-of-scope files or re-baseline the task.
If post-merge verification fails, stop integrating other branches. Keep the failing result isolated until the failure is understood and the target branch is restored or fixed.