These rules apply repository-wide, including internal/compatibility/. The docs site also follows docs/AGENTS.md.
Write prose paragraphs and list items as single source lines and let editors and renderers wrap them naturally. Do not hard-wrap Markdown at a fixed column; preserve separate lines only where Markdown syntax or intentional formatting requires them, such as headings, lists, tables, blockquotes, and code blocks.
pnpm install && pnpm -r build # build the workspace
SCRIPTC_TEST_WORKERS=4 pnpm test # full gate: plain lane
SCRIPTC_TEST_WORKERS=4 SCRIPTC_SAN=1 pnpm test # full gate: sanitized laneThe ordinary workspace build does not rebuild packaged native artifacts. When changing native assembly/object emission or runtime-pack selection, install CMake, Ninja, the pinned LLVM 22 development package, and Zig 0.16.0, then run the matching @scriptc/llvm-<platform> and @scriptc/runtime-<platform> build:native scripts explicitly. The macOS full test suite also needs its generated artifacts.
Use focused local tests while iterating, then run both full lanes locally before shipping. Both lanes green is the bar before shipping any change. Linux hosts run their supported native-clang contracts locally, and macOS retains the Darwin-native contracts.
SCRIPTC_TEST_WORKERS caps the vitest worker pool so concurrent agents don't
contend for cores. Direct local runs default to two workers, two nested native
compiler jobs per worker, and one Cargo job. Full local suites share one
cross-lane advisory lock.
On a workstation that must remain responsive, wrap focused or local commands with the repository's hard resource limiter:
pnpm limit -- pnpm exec vitest run <test-file> -t <focused-test> --maxWorkers=1The wrapper uses a transient user cgroup and defaults to half of one CPU, a
2 GiB memory-high threshold, a 3 GiB hard memory limit, no swap, and one test,
native compiler, and Cargo worker. Override the ceilings only through the
documented SCRIPTC_LIMIT_* environment variables when the machine has spare
capacity.
Corpus programs are differential tests against Node: every program runs under Node and as a compiled native binary, and stdout, stderr, and exit codes must match byte-for-byte. A new feature lands with corpus programs that pin its behavior both ways.
After completing and checkpointing an implementation step, fetch origin/main
and reconcile it before starting the next step. Preserve concurrent agents' WIP:
inspect divergent work and integrate it deliberately instead of overwriting or
resetting another worktree.
Test location follows scope:
- Co-locate white-box unit tests with implementation files under
packages/*/src; name them after the source file (cc.ts→cc.test.ts). - Put package-level API and integration tests in
packages/*/test. - Put cross-package differential, harness, and end-to-end tests in the root
tests/tree.
Keep existing tests in place unless a change already touches their organization; new tests should follow this convention.
packages/compiler— the frontend (tsc API to IR), the typed IR with validator and serializer, and the LLVM, C, and Rust backends.packages/runtime— the C runtime compiled into every C/LLVM-backed scriptc binary.packages/runtime-rust— the memory-safe Rust runtime (#![forbid(unsafe_code)]) linked by--backend rustbinaries; its own gate iscargo test+cargo clippy -- -D warningson the toolchain pinned in itsrust-toolchain.toml(CI jobruntime_rust).packages/cli—scriptc build | run | coverage.internal/compatibility— generated Node.js parity inventory, implementation-owned compatibility manifests, and engineering backlog.tests/— the differential corpus, diagnostics snapshots, and harness.docs/— the standalone Next.js App Router + MDX documentation workspace.scripts/— repository tooling, including the release version stamp.
The Node.js compatibility system is an implementation inventory and planning tool. Keep its claims narrower than the evidence. Do not change a status merely to make the public matrix look correct.
internal/compatibility/node-v24.jsonpins the Node release, commit, official API inputs, and documentation URLs. The pinned Node API is the denominator.- Node's official documentation defines the public API hierarchy and stability metadata. Consult Node source when the docs do not fully specify behavior.
- Running the pinned Node release is the behavior oracle for differential tests.
- Compiler source and
packages/compiler/surface-manifest.json, augmented byinternal/compatibility/static-support.json, define static-native support. internal/compatibility/dynamic-support.jsondefines dynamic-island modules, exports, members, explicit stubs, globals, and policies. ItsruntimeRegistryconfiguration references the full bootstrap registration and export tables, with explicit engine-specific overrides, to generate the runtime builtin registry. Keep linkage broader than the test-backed compatibility inventory; missing evidence must not remove existing runtime exports.- Test evidence determines positive behavioral claims. Source manifests alone do not prove Node-compatible behavior.
Treat static-native and dynamic-island compatibility independently. Support in one tier says nothing about the other, and --dynamic does not turn an unsupported typed call into a supported static call.
supported— broadly implemented. This requires test evidence.partial— a useful implemented subset. This requires test evidence, but evidence may cover an API family rather than every documented overload.refused— a named compiler diagnostic or explicit dynamic throwing or rejecting stub exists. Never infer this status from absence.not-implemented— no implementation claim. Its verification basis decides whether it is ready to implement or must first be investigated.by-design— intentionally outside a scriptc execution model. This requires an explicit implementation-owned architectural policy.unreviewed— no compatibility subsystem owns a reliable classification. Do not treat this as unsupported or as an implementation ticket.not-applicable— documentation, configuration, executable behavior, or an embedding model that does not map to the execution tier.
Chapter rows are generated summaries of their descendants, never independent support claims.
Read a row's verification basis before acting:
test-backed— mapped to implementation and relevant tests. Audit the exact overload or behavior before broadening apartialclaim.explicit-refusal— source-verified refusal boundary.verified-absence— a runtime presence test confirms absence.declared-gap— an implementation-owned manifest explicitly declares the gap; it is ready for implementation.registry-gap— no implementation registry entry matched. Verify the gap before implementing: it may be an incomplete manifest or symbol match.architectural-policy— explicit intentional exclusion.unreviewed,not-applicable, andderivedretain the meanings above.
pnpm node-compat:backlog emits tier-specific work. Its actions are:
verify-gap— investigate a registry gap first; this is not yet an implementation ticket.implement— implement a declared or verified gap.replace-refusal— add support where an explicit refusal currently exists.audit-partial— identify and implement missing behavior in a tested subset, or tighten an overbroad mapping.classify— establish ownership and evidence for an unreviewed row.
Backlog priority is a heuristic based on Node stability: stable APIs are raised, release-candidate or unrated APIs remain normal, and experimental, deprecated, and legacy APIs are lowered. It is not a product roadmap.
Rows are not necessarily independent work items. One implementation can close a class, methods, overloads, aliases, and nested rows. Plan work by coherent API family and inspect the regenerated diff instead of creating one ticket per row.
Useful queue filters compose:
pnpm node-compat:backlog --action=implement --priority=high
pnpm node-compat:backlog --action=verify-gap --chapter=assert
pnpm node-compat:backlog --tier=dynamic --format=tsvNever hand-edit these files:
packages/compiler/surface-manifest.jsoninternal/compatibility/generated/node-v24-internal.jsoninternal/compatibility/generated/node-v24-backlog.jsondocs/src/generated/node-v24-compatibility.jsondocs/src/generated/node-v24-compatibility-meta.jsonpackages/runtime/src/scr_island_manifest.h
Change compiler decision sources or the implementation-owned compatibility manifests, then regenerate. Use pnpm manifest when compiler decision tables change and pnpm node-compat whenever the Node pin, compatibility manifests, compiler surface manifest, or dynamic registry changes.
The public docs artifact must not expose repository test paths, internal source identifiers, engineering evidence bookkeeping, documentation/metadata headings, command-line or configuration-only entries, or rows that are N/A in both tiers. Those remain in the complete internal census under internal/compatibility/generated/.
For each API family:
- Start from the backlog action and verification basis. Verify every
registry-gapbefore assuming code is missing. - Read the pinned Node documentation, stability metadata, relevant Node source when needed, and existing scriptc implementation paths.
- Decide static-native and dynamic-island scope separately.
- Implement the smallest coherent behavior family. Preserve explicit refusals for unimplemented forms rather than silently diverging.
- Add differential evidence that executes under Node and as a compiled binary. Cover stdout, stderr, exit status, success behavior, and important error shapes. Dynamic-island work needs island-executed differential fixtures; a load-only export check does not prove behavior.
- Update the implementation-owned source or manifest. Positive
supportedandpartialmappings must name existing test evidence. - Run
pnpm manifestif compiler surface tables changed, then runpnpm node-compat. Never patch generated JSON or headers. - Inspect the internal ledger, public artifact, and backlog diff. Confirm the intended API family changed and unrelated rows did not.
- Run focused tests,
pnpm node-compat:check, and the full validation gate required for the change. If docs output changed, also run the docs gate.
Compatibility commands:
pnpm node-compat
pnpm node-compat:check
pnpm --filter @internal/compatibility check:upstream # networked pinned-input checkThe docs site is a standalone pnpm workspace under docs/. It uses Next.js App Router + MDX, with one topic per docs/src/app/<topic>/page.mdx.
- The project is scriptc, lowercase, everywhere: page titles, prose, and code samples.
- Name-bearing strings such as the site name, tagline, GitHub URL, and canonical origin
https://scriptc.devlive indocs/src/lib/site.ts. - Coverage numbers on a page may only be output from
scriptc coveragefor a specific program shown in the same block; never publish invented aggregate coverage statistics. - Every shell command shown in a fence must have run successfully against the current build. Output blocks contain real output; trimming is acceptable, invention is not.
- Document limitations plainly on the limitations page rather than scattering them as fine print.
- Use literal HTML
<table>markup in MDX, never Markdown pipe tables. - Fence info strings contain the language and optionally a filename after a colon, for example
ts:src/main.ts; no other fence metadata survives. - Use
consolefences for shell sessions ($ commandfollowed by output). - Use
difffences for additions and removals. - Use HTML
<dl>/<dt>/<dd>for flag and subcommand references.
- Add
docs/src/app/<topic>/page.mdx. - Add
docs/src/app/<topic>/layout.tsxexportingpageMetadata("<topic>"). - Add the slug to
PAGE_TITLESindocs/src/lib/page-titles.ts. - Add navigation in
docs/src/lib/docs-navigation.ts; it also drives mobile navigation and the sitemap.
Do not start duplicate docs dev servers. Reuse the existing server and its hot reload process when one is running. CI-style builds must use a separate dist directory so they do not corrupt the dev server's .next state:
cd docs
NEXT_DIST_DIR=.next-check pnpm checkpnpm check runs the compatibility drift check, TypeScript check, and production build. It is required before landing docs or compatibility output changes.
Releases are maintainer-run; see RELEASING.md.