For AI agents: this page as Markdown · llms.txt · JSON Schema

Rule packs

A rule pack is a named set of rules, published as its own npm package. Core ships no packs and no rules.

Label Whose Package
By Rerail Rerail's @neralabs/rerail-pack-<id>
By publisher (By @acme) a third party's, not reviewed by Rerail (Third parties' packs) rerail-pack-<id> or @<scope>/rerail-pack-<id>
Created by you yours, in no pack none

Rerail installs the packs you choose into $RERAIL_HOME/packs. A pack runs only where a repository adds it (Rule scoping).

What a pack holds, who adds it, how packs are recommended

A pack directory holds rule documents in rules/ and a pack.json. The pack.json lists them with the metadata the dashboard shows (a title and group per rule), a match definition, and whether its rules start on.

Set-up installs and adds the packs the person confirms. Afterwards the dashboard adds and removes packs and turns their rules on or off, for the repository. The API and its MCP tools add and remove packs, turn rules off or change their mode, per repository, folder and checkout.

Recommendations match the repository's files and dependency manifests against each match definition. Matching is data only: no pack code, no model, no reading code for violations, no network but npm to fetch the packs. Rerail says nothing about what no pack covers.

The packs

Every pack lives under packs/ in this repository and is published from there (pack format), except a private pack ("private": true in its package.json): it is never published and is not in src/engine/catalog/pack-list.generated.ts; install it from a checkout with rerail packs add packs/<id>, and set-up then offers it from that copy.

Pack Category Rules (on) Recommended for
Cyclomatic complexity language 7 (7) Python, TypeScript, JavaScript, PHP, Blade, Rust or Vue source outside tests. See Cyclomatic complexity.
JavaScript language 121 (36) JavaScript or TypeScript source. ESLint core rules apply to both; eslint-plugin-unicorn rules to JavaScript files only.
TypeScript language 65 (23) a typescript dependency, or *.ts/*.tsx files
Python language 537 (318) Python source outside tests, scripts and docs
PHP language 15 (5) PHP source: *.php files, not Blade views
Blade language 10 (8) Blade templates: *.blade.php files
Rust language 33 (17) *.rs files
SQL language 21 (19) *.sql files
Next.js framework 21 (21) a next dependency
Node.js framework 37 (11) a Node.js server framework (express, koa, fastify, @nestjs/core and others) or electron dependency, or *.cjs/*.mjs/*.cts/*.mts files outside tests, scripts, fixtures and configuration
Vue framework 23 (21) *.vue files
React framework 40 (21) a react dependency, or *.jsx files
Pack list, own packs and starting values in detail

Core lists pack names in src/engine/catalog/pack-list.generated.ts (npm run generate:packs); each pack.json says what the pack fits. Rerail recommends a pack only if at least one of its rules starts on; otherwise it would run nothing.

A project's own pack holds rule documents in rules/ and a pack.json whose match definition names the files or dependencies that identify the project. Its package.json is named rerail-pack-<id> or @<scope>/rerail-pack-<id>, with core's range under peerDependencies.

Starting values also apply to rules a later upgrade adds.

Installing packs

All packs install into one npm prefix, $RERAIL_HOME/packs, with the person's npm login. npm runs no install script.

Why one prefix

Packs never install into a project's node_modules: one Rerail serves every repository on the machine, and Python or PHP repositories have none. Nor into core's install, which rerail update replaces. Installing uses the npm login that installs Rerail, so whoever can read @neralabs/rerail can read every pack.

Set-up

npx @neralabs/rerail setup in a repository, or /rerail-start in one not set up, offers packs in five steps.

  1. Fetch every listed pack into a staging folder. Nothing in the repository is read until the person allows it.
  2. Keep newer versions the computer already has installed.
  3. Ask which fitting packs to install.
  4. Install only what the answer carries: "Yes", "Change rule packs…" (--answer choose), "Not now" (--answer not-now) or "Skip, I'll pick packs".
  5. Verify the catalog still loads every pack; otherwise put the change back and ask again.
Each step in detail
  1. Fetch. Set-up fetches every listed pack under the update channel's npm dist-tag: latest by default, where each pack version is published (pack format), or next for a pack with no latest. They land in a staging folder under $RERAIL_HOME/packs/staging, which installs nothing and is removed once read. Each is checked as the catalog does.
  2. Keep newer versions. A pack installed at a newer version than the tag's (rerail packs add typescript@next) is offered at that version. If it does not load (its core range, after setting up an older core again, say), set-up names it as unusable, with why, and does not offer it. An answer naming an older version than the installed one is refused; rerail packs add downgrades.
  3. Ask. Set-up lists each fitting pack with its rule count and why it fits, and the rules the repository's lint config turns off or configures. It warns when a yes replaces an installed version, which every repository on the computer runs. A fitting pack with no rule that starts on is named as having no rules yet, as is every such pack under "Skip, I'll pick packs".
  4. Install only what the answer carries.
    • "Yes" installs the versions shown, from npm's cache where discovery left them; a coding agent passes --packs typescript@0.1.1,….
    • "Change rule packs…" (--answer choose) installs only the packs picked from those fetched, replacing any an earlier dashboard review added. Not offered if no pack was fetched.
    • "Not now" (--answer not-now), or asking again after a bad answer, installs nothing.
    • "Skip, I'll pick packs" reads nothing in the repository. It installs the fetched packs picked and adds each for the whole repository without its lint config, or none ("None for now", --packs none).
  5. Verify. The catalog must load every pack named and every pack it loaded before. If npm fails or a pack is left out (core range, a rule another pack holds), every changed pack is put back as npm recorded it (a folder pack from its folder) or removed. Set-up asks again, saying why: no read access, npm not logged in, registry unreachable, or why the pack was left out. An installed version stands in for one npm cannot give.

rerail packs

Arguments and refusals

Locking, reloads and core ranges

Pack changes run one at a time, a running daemon picks them up without a restart, and each pack names the core versions it fits.

Lock, reload and core-range details

Catalog checks

The catalog checks each installed pack on its own. A failing pack is left out and reported (rerail packs list, GET /api/rule-packs skipped); the others load.

A pack must pass:

  1. Package: package.json is a pack's, named for its pack.json id and installed under that name.
  2. pack.json: valid, and every listed document exists.
  3. Core range: includes the active Rerail's version.
  4. Compilation: every code rule compiles.
  5. No clash: no other loading pack has its pack id or one of its rules.
Which packs are read, exact checks and left-out reasons

The catalog reads only packs the packs folder's package.json lists as its own dependencies. A pack npm installed only as another package's dependency is never read. It is reported as left out, saying it is not in that package.json, with rerail packs add <package> as the fix. If that package.json is missing or unreadable, every pack on disk is reported that way, with why, not as no packs.

  1. Package. package.json matches rulePackPackageSchema, named rerail-pack-<id> or @<scope>/rerail-pack-<id>. So @neralabs/rerail-pack-foo in a rerail-pack-foo folder is left out (it is installed as rerail-pack-foo, but its package.json names @neralabs/rerail-pack-foo): no package borrows Rerail's publisher.
  2. pack.json. Valid (rulePackFileSchema, match included); every listed document carries the listed id.
  3. Core range. Prereleases included.
  4. Compilation. One refused only as language-not-supported loads, inactive until the engine reads its language; any other refusal leaves the pack out.
  5. No clash. Rerail's own wins (pack id sql is taken by @neralabs/rerail-pack-sql); between third parties, the first by package name. Install location never decides. While Rerail's own pack for an id is installed but left out (by its core range, say), the other is left out too (pack id sql is kept for @neralabs/rerail-pack-sql, which is installed but left out). Repositories adding the id run nothing until Rerail's loads.

Reasons are written for the person reading set-up, rerail packs list or rerail update:

Third parties' packs

Anyone can publish a pack to npm; Rerail loads it like its own but has not reviewed it.

Install it with rerail packs add <package>, or from its tarball or folder. Its rules run in Log until a person approves this version with rerail rules approve <package> (approval).

Naming, publisher and how each surface marks it

It must be named rerail-pack-<id> or @<scope>/rerail-pack-<id> (<id> from pack.json) and pass the checks above. Its rules run in every repository that adds it, and their steer text reaches the coding agent.

Publisher is rerail for @neralabs/*, else the npm scope (@acme), else the package name (rerail-pack-lint). A pack installed from a folder or tarball (rerail packs add <folder>, recorded by npm as file:<path>) counts as unpublished and not reviewed, whatever its package.json says, @neralabs/* included. It still loads, and clashes, by its folder's package name.

Surface Third party's pack From a folder or tarball
rerail packs list package name; waiting for approval adds (from the folder <path>)
rerail packs add warns once per pack (below) … was installed from the folder <path>, not from npm, and Rerail has not reviewed it.
GET /api/rule-packs approval installedFrom: "folder" (or "tarball")
Dashboard By @acme · not reviewed From a folder · not reviewed (third party: By @acme · From a folder · not reviewed), never By Rerail
Set-up table Acme SQL (by @acme) SQL (from a folder)
Steer note From the Acme SQL rule pack by @acme, which Rerail has not reviewed. From the SQL rule pack, installed from a folder, which Rerail has not reviewed.
Dashboard, warnings, approval, clashes and updates

A worktree's own rules

A checkout can carry its own rules in <checkout>/.rerail/rules/, with no pack, install or attachment.

Each .json rule document directly in that folder is one rule. Rerail reads the folder as pack worktree, for that checkout only. Each rule runs in Log until a person approves it (rerail rules approve <id>, in the checkout).

What it reads, settings, edits and file checks

A rule document is a rerail.policy/v1 code rule or a conversation rule; no pack.json or package.json is needed. The pack is present for the whole checkout without being added. No other checkout runs or lists it, including the main checkout and linked worktrees. It runs only while the checkout is started and its repository set up (rule scoping).

Where it shows, and trust

Where a pack's rules live

Rule documents live only in the installed pack; the database holds people's decisions about them, by id.

Because those decisions name packs and rules by id, they apply to whatever version is installed now.

What the database holds, and what happens when packs change

Core reads each installed pack's pack.json and listed documents into memory, re-reading them when installed packs change; nothing copies them into the database. The database holds attachments (which packs and rules each repository adds), settings (on, mode, suppressions, option values in config) and the library (rules people write, or copy and edit).

Case What happens
A pack is upgraded New documents apply once installed; settings and attachments keep applying by id. Added rules start with the pack's on and mode. Removed rules disappear; settings naming them do nothing. A config naming an option the document no longer declares stops that rule, with the reason, as any such config does.
A pack is downgraded The same as an upgrade.
A pack is uninstalled It leaves the catalog; repositories adding it report it as unavailable (missing). Settings and library copies stay, so reinstalling restores the configuration.
Two versions of a pack are installed Only one can be. Rerail installs into $RERAIL_HOME/packs and reads only the node_modules/rerail-pack-<id> and node_modules/@<scope>/rerail-pack-<id> the packs folder's package.json lists, one version each.
Two packages hold one pack id Rerail's own loads, else the first by package name; the other is reported as left out. Repositories add packs by id, so they run the one that loads. While Rerail's own is installed but left out, neither loads.
A pack's core range excludes the running core Skipped and reported; repositories adding it report it as missing.
A person edits a pack's rule The rule is copied into the library and added where the original is. The copy keeps the edit through upgrades; the original follows them. Saved in the dashboard, the copy is approved and the original switched off where the copy is added. A coding agent's copy runs in Log, beside the running original, until a person approves it (copy on edit). Option values change through config settings instead.
rerail rules export / import rules.json carries attachments and settings by id, never pack rules. Without the pack, its attachments are missing until it is installed.

Format

A pack is packs/<id>/pack.json, in the rerail.rule-pack/v1 format, beside the rule documents it lists in packs/<id>/rules/.

Every pack.json field, with an example, is in pack-format.md, "pack.json"; what every pack must prove is its validation contract.

Matching

Rerail lists the checkout's files once, reads its dependency manifests, and matches every pack against them. No source text is read.

Each fitting pack comes back with its rule count (rules that start on), reason and the folder where set-up adds it.

Which files and manifests count, and how the folder is chosen

A language or framework pack goes in the folder holding all its matches (web when only web/ has TypeScript; the whole repository when matches are spread). A repository pack goes in the whole repository.

Cyclomatic complexity

The complexity pack flags functions with cyclomatic complexity above 6, reproducing the Metrics-in-the-Loop study's feedback.

A finding names the function and number: CYCLOMATIC_COMPLEXITY src/grade.py:1: `grade` has cyclomatic complexity 8, over the ceiling of 6. Decompose it into smaller functions.

How complexity is counted

Complexity is one plus a function's decision points, as lizard 1.24.0 counts them minus its tokenizer defects, in Python, TypeScript, JavaScript, PHP, Blade, Rust and Vue. Blade directives and Vue templates run in no function and are never scored; nor are test files.

packs/complexity/README.md has per-language counting, deliberate differences from lizard, the real-code comparison and the proving corpus.

Using it like the study

The pack matches the study's C arm: same tool semantics, same limit, only the functions the agent changed.

  1. Add the complexity pack. Set-up offers it for Python, TypeScript, JavaScript, PHP, Blade, Rust or Vue source. Or run rerail packs add complexity, then repository_attachments_set (PUT /api/repository/attachments, body { "checkout": "<absolute path of a checkout>", "add": [{ "sourceKind": "pack", "sourceId": "complexity" }] }, or "repository": "<repository key>" in place of checkout).
  2. Keep the default ai-output audit scope, which reports only functions the agent changed.
Attachment rules, audit scopes and differences from the study

The attachment covers the whole repository unless the entry names a folder. A coding agent may make this call only before set-up; afterwards a person does. Its rules start on.

A finding covers its whole function, so any touched line reports it. Under touched-files (rerail settings --session <session-id> --scope touched-files), every over-limit function in an edited file is reported, touched or not; --scope ai-output restores the default.

Differences from the study:

API

The daemon serves packs over HTTP; each call is also an MCP tool.

Response fields and scan limits