For AI agents: this page as Markdown · llms.txt · JSON Schema
#Releasing Rerail
How core is built, versioned and published. Rule packs release separately; see pack-format.md.
#Release
Merging the Release pull request tags a version; the tag builds, checks and publishes core.
#Build
.github/ builds the package the updater installs, via .github/, and
test-installs it on each platform before publishing.
Build and check steps
- Compile the native kernel for macOS, Linux glibc and Windows MSVC, on arm64 and x64.
- Linux builds against glibc 2.17 with napi-rs's cross toolchain; a kernel needing glibc newer than 2.28 is rejected.
- A Windows kernel importing the Visual C++ runtime is rejected. The kernels link it statically, so a clean Windows install needs no redistributable.
- Smoke-test native loading and device storage on each host.
- Build the runtime and bundled UI; pack only if all six
native/*.nodefiles are present. - Install the tarball into a scratch prefix; check
rerail --help, the bundled UI andnativeHealth(). - On Windows x64, Linux x64 and macOS arm64, install it as a user would and exercise the managed runtime:
scripts/on Windows, the environment matrix'shost/ windows-install-smoke.ps1 scripts/throughhost/ azure/ guest.sh matrix.mjs localon Linux and macOS. Nothing publishes unless all three pass.
#Release pull request
.github/ refreshes the Release pull request on every push to main. It also carries the
rule pack bumps (docs/pack-format.md).
- Bump: if anything core ships changed since the last
v<version>tag, it bumps to the next patch (0.1.0→0.1.1) and writes itsCHANGELOG.mdsection. - Merge: tags the merge commit
v<version>, which startsrelease.yml.
Exact bump and tag details
- The bump changes the root entries of
package.jsonandpackage-lock.json. TheCHANGELOG.mdsection comes from the unreleased lead and pending.changelog/entries. - The tag is pushed with the
neralabs-ciapp's token. - Anything core ships: every path except packs, documentation, CI, changesets and pending changelog entries, tests,
fixtures, corpora, benchmarks, development and release scripts, and repository files npm does not pack.
package-lock.jsoncounts only when more than its pack entries moved (NOT_SHIPPEDinscripts/).release/ core-paths.mjs
#Installer plugins
The same bump re-pins the installer plugins (plugins/claude, plugins/codex, plugins/cursor) to the version just
released. Publish a new plugin version only once the release is on npm.
Per-store publishing, pins and stamping
- Claude: developer portal; follows
main, live when a reviewer publishes it. - Codex: upload a new ZIP of
plugins/codexat platform.openai.com/plugins. - Cursor: Cursor's team reviews each repository update by hand.
- The bump updates each manifest's
versionand thenpx @neralabs/itsrerail@<version> installskill runs (PLUGIN_PINSinscripts/release/core.mjs). - Checked-in manifests carry no version.
npm packstampspackage.json's version in (prepack) and restores the source form (postpack). After a failed pack, runnode scripts/.build/ stamp-plugin-versions.mjs restore
#Tags and channels
A v<version> tag, equal to package.json's version, publishes @neralabs/rerail to npmjs on the channel it names,
after a reviewer approves.
| Tag | Channel |
|---|---|
v0.2.0 |
latest (stable) |
v0.2.0-beta.1 |
beta |
v0.2.0-next.1 |
next |
betaandnextare opt-in:rerail update --channel <tag>.- The Release pull request makes only plain versions, for
latest. For abetaornextpreview, set the version and push the tag by hand.
Publishing rules and checks
- Publishing uses npm trusted publishing from the protected
releaseenvironment. The channel is the npm dist-tag. - Other prerelease suffixes are rejected. The run refuses a preview older than core's
latestbefore building. nextnever trailslatest: alatestrelease raisesnextto it (scripts/) unlessrelease/ raise-next.mjs nextholds a newer preview. Whoever followsnextgets every stable release. Abetaornextrelease leaves other tags alone.- Re-running the publish job is safe: npm refusing to publish over an existing version counts as published.
- The run's summary lists core's dist-tags. It fails if the channel's tag is not the new version, or, for
latestandnextreleases,nextis unset or older thanlatest.
#Manual runs and put-back
workflow_dispatch builds a trial tarball, retries a failed release, or puts latest back to an earlier version.
- No inputs: uploads the tarball as a run artifact, unpublished. Try it with
npm install <tarball>. publishset to a version: retries that version's release frommain; see below.latestset to an earlier released version: builds nothing and pointslatestback to it.
Retrying a failed release
Release tags can't be moved or deleted, and re-running a failed run reuses the tag's scripts. So when a release fails
before publishing because of something that doesn't ship, such as a workflow or a release script, fix that on main
and run:
gh workflow run release.yml --ref main -f publish=<version>The run builds main, runs every check and publishes behind the same approval. The tag stays where it is, and npm's
provenance names the main commit. It is refused unless all of these hold, so core's own code is the tag's:
- it runs from
mainwithlatestleft empty, andmain'spackage.jsonnames that version; - the
v<version>tag exists; - nothing core ships has changed since the tag (
publishRetryRefusalinscripts/).release/ core-paths.mjs
How the package is built and its changelog can still differ from the tag, so that a fix to the build can be retried
too. The run summary lists every path changed since the tag, starting with those that can change the tarball (PACKAGE_INPUTS): the build
workflow and its actions, the shrinkwrap step, .gitattributes and CHANGELOG.md. Check that list before approving.
If the fix changes what ships, it takes the next version: the Release pull request offers one as soon as the fix is on
main. Say in that version's lead that the failed version was never published, since its notes won't reach anyone
updating past it.
What a put-back also does
The put-back uses trusted publishing behind the same approval as a release, so no one needs an npm login to put core
back. If next is older than that version, it raises next to it too. The run's GitHub Release job then makes
that version's Release the repository's latest.
#Build cache
.github/ keeps the native builds' cargo caches warm. After changing Cargo.lock, the Cargo
manifests, .cargo/config.toml or rust-toolchain.toml, let it finish before merging the Release pull request.
Otherwise the release builds cold.
How the caches are used
- A tag's run restores only caches saved on
main;package.ymlrestores only the exact match. - On
mainit runs nightly and after changes to those files. Each target lacking a matching cache builds from scratch and saves one.
#Environment matrix
.github/ builds the same package after each push to main that can change what ships. It
installs it as a user would; see docs/environment-matrix.md for environments, checks and manual
runs.
Machines and user path
- Throwaway Azure VMs: Ubuntu and Debian on x64 and arm64, RHEL on x64, Windows 11 on x64 and arm64.
- GitHub-hosted macOS runners: macOS 26 and 15 on Apple silicon, macOS 15 on Intel.
Each follows a user's path: npx @neralabs/, the managed runtime, PATH in a new shell, the coding-agent
hooks, daemon restart and removal.
#Release notes
Release notes pitch the release to people who opt into each update; the GitHub Release publishes them unchanged.
- Entries. Each pull request changing what core ships adds one file in
.changelog/, its name opening with its change type (added-,changed-,deprecated-,removed-,fixed-,security-; .changelog/README.md): one or two plain sentences for the upgrader, at most 60 words. - Lead. Whoever readies the release writes a paragraph under
## Unreleasedat the top ofCHANGELOG.mdonmain: what the release gives and why to take it. - Format. From 0.1.1 the notes follow Keep a Changelog 1.1.0, with the two
differences
CHANGELOG.md's header states: headings read## <version> (<date>), and pending entries wait in.changelog/rather than under## Unreleased. A pack's notes take the same format and types, its lead and rule entries written frompack.json(pack-format.md). - Versions. Below 1.0.0 a release is a patch unless it names another, and a patch can change or remove behavior,
which Semantic Versioning allows below 1.0.0. To release a minor or major, name it in the heading:
## 0.2.0 (unreleased). What core promises from 1.0.0 is not set yet.
Why, assembly and CI checks
Entry files mean pull requests never edit the same lines of CHANGELOG.md. Entries leave detail to the docs they link,
and leave out commit hashes, ticket ids, pull request numbers and repository-internal maintenance. The lead goes before
the first entry; add the ## Unreleased heading if the last release took it. It belongs on main because every push
rewrites the Release pull request.
Assembly. The Release pull request renames the heading ## <version> (<date>), dated the day it last updated from
main. It puts pending entries under the lead, under a ### <type> heading for each change type that has one (Added,
Changed, Deprecated, Removed, Fixed, Security, in that order, file-name order within a type), and deletes their files.
With neither lead nor entry, the section says no entries were written. Should an entry with no type reach main, core
is left out of the Release pull request, and its description says why, until the file is renamed; the packs still
release.
CI (scripts/, in ci.yml's lint job) fails:
- a pull request with a pending
.changelog/entry that has no change type, or whose added or changed entry runs long, names a ticket, pull request or commit, or starts with its own-; - a pull request writing an entry into
CHANGELOG.md's first section, under## Unreleasedor the last release's dated heading; - the Release pull request until the dated section has its lead. One pull request releases core and packs, so this also holds back the packs' release.
#GitHub Releases
Every released version, core's and each pack's, gets a GitHub Release on its tag, with its CHANGELOG.md section as
notes.
Backfill creates Releases for tags without one, such as older releases or runs that failed before their Release:
- Sync tags:
git fetch --tags --prune --prune-tags(add--forcefor a tag rejected as changed). - Preview:
node scripts/.release/ github-release.mjs --backfill --dry-run - Run it without
--dry-run; your gh login needs repository write access.
How Releases are made, and which tags backfill skips
scripts/builds them, so notes are written once.release/ github-release.mjs - After the dist-tag check,
release.yml's GitHub Release job makes core's, titledv<version>: a pre-release fornextandbeta, the repository's latest release forlatest. - A version without its own changelog section says no changelog entries were written.
- Re-runs rewrite an existing Release's notes, pre-release and latest flags. After a put-back, the restored version becomes the latest release.
release-packs.ymlmakes a pack's Release (pack-format.md).- Step 1 makes the checkout's tags the remote's. The dry run prints each Release it would create, with notes from the changelog at the tag's commit.
- Backfill reads only the remote's tags, and names each tag it skips: local-only, local commit differing from the remote's, or no changelog section for its version. So every Release it makes has notes.
- Only the highest core version on
latestbecomes the latest release.
#Shrinkwrap
The tarball ships an npm-shrinkwrap.json, so installs get exactly the dependencies package-lock.json pinned.
- Write failures. Run
npm installand commit the lockfile. - Failed pack. Remove the leftover with
node scripts/, or npm would use it instead ofrelease/ shrinkwrap-pack.mjs remove package-lock.json.
How the shrinkwrap is written and checked
- The checkout's only lockfile is
package-lock.json; it never commits a shrinkwrap. - npm honors a published shrinkwrap instead of resolving versions and integrity hashes fresh; the registry flags such
versions (
_hasShrinkwrap). - Writing.
npm packwrites it (prepack:node scripts/) and deletes it after (release/ shrinkwrap-pack.mjs write postpack);package.json'sfileslists it. - Contents. The root and every package its dependencies reach in
package-lock.json: no development dependency, workspace pack, or optional peer nothing else needs. Packages reached only through optional dependencies are markedoptional. - Write failures. Writing fails if
package-lock.json's root dependencies differ frompackage.json's, or the lockfile lacks a package a dependency needs. - Release check. Before publishing,
release.ymlrunsnode scripts/. It fails on a missing shrinkwrap or one differing from the tag'srelease/ shrinkwrap-pack.mjs check <tarball> package-lock.json, naming the packages that differ. - Layout. npm installs a shrinkwrapped package's dependencies inside it, under
node_modules/@neralabs/, not beside it.rerail/ node_modules
The managed install (src/setup/updater.ts) runs no install scripts (--ignore-scripts). A new dependency needing an
install script would break after rerail update; check before adding one.
Install-time verification
- No production dependency needs an install script;
better-sqlite3ships prebuilt binaries. - Before running staged code, it verifies registry signatures and provenance attestations of everything installed with
npm audit signatures, stopping on any failure. - A registry without signing keys, such as some private mirrors, gets a warning and the install continues.