01 / Hard prohibitions
Keep text measurements distinct
The guide prohibits treating byte counts, code points, graphemes, and terminal cells as interchangeable. Naming each unit makes the rule specific to terminal layout rather than a generic warning about strings.
Source excerpt starting at line 10.10- Do not interchange byte lengths, code points, graphemes, and terminal display-cell widths.
02
Make ownership a requirement on every exit path
Handles and callbacks are treated as resources that need explicit cleanup, alongside buffers and listeners. The same rule connects bounded input work with tests for lifecycle failures.
Source excerpt starting at line 8.8- Make native ownership explicit; clean up handles, callbacks, buffers, and listeners on every exit path. Bound9 input-driven work and test lifecycle failures.
03 / Verification by change type
Choose checks by the boundary a change crosses
The verification section separates ordinary TypeScript edits from native and cross-package output changes. Runtime, FFI, build, and export changes also trigger Node and packaged-distribution checks.
Source excerpt starting at line 24.24- Ordinary TypeScript source changes do not require the root build. Use the affected package's `test`, `typecheck`,25 `build`, or validation scripts as applicable.26- Run `bun run build` from the repository root after native or cross-package build/output changes, or when tests report27 a missing/stale native artifact. It does not build web or examples; use their package scripts.28- For native changes, run `bun run test:native` from `packages/core`. Filter with `bun run test:native -Dtest-filter="test name"` while iterating.29- For runtime, FFI, build, or export changes, run the relevant Node and packed-distribution scripts from that package30 (such as `test:js:node` or `test:dist`) when present.
04
Use a shared FFI contract
The portable-signature rule names backend-only types to avoid and the JavaScript representations to use instead. It gives contributors a concrete contract for crossing the Bun and Node boundary.
Source excerpt starting at line 40.40- Portable symbol signatures must stay within the `node:ffi`/`bun:ffi` intersection. Use explicit widths such as41 `u32`/`u64`, not backend-only ABI names such as `usize`, `napi_env`, or `napi_value`; represent `i64`/`u64` as `bigint`,42 native booleans as `0`/`1`, and shared pointers as `number | bigint`.
05
Stabilize storage before taking its address
A retained native pointer requires stable backing storage and a live owner. The guide gives the required operation order and explains how reversing it can invalidate the address.
Source excerpt starting at line 52.52- Use `ptr(view)` only when native code stores the address beyond the call. Before resolving it, access `view.buffer` to53 move any inline typed-array storage into a stable `ArrayBuffer`, then keep the view alive for the complete native54 lifetime. The order is required: `const owner = view.buffer; const address = ptr(view)`. Calling `ptr(view)` first and55 accessing `view.buffer` later can move the storage and invalidate `address`.