Guardrails
Do not edit this file. pnpm census writes it from the live registries.
Guardrail rules (124 distinct)
rule_tool_check runs these rules. The repository also runs them on itself.
An advisory rule reports a finding and does not fail the check.
67 rules run on every check. The other rules run when the check names their skill.
One rule can be in two families, so the family counts add up to more than the total.
| Family | Rules | Applies to |
|---|---|---|
rule-backend |
6 | A check for this skill |
rule-data |
4 | A check for this skill |
rule-devops |
2 | A check for this skill |
rule-devops-docker |
2 | A check for this skill |
rule-frontend-angular |
1 | A check for this skill |
rule-frontend-react |
2 | A check for this skill |
rule-frontend-svelte |
1 | A check for this skill |
rule-frontend-vue |
1 | A check for this skill |
rule-process-adr |
6 | A check for this skill |
rule-process-ai |
15 | Every check |
rule-process-cargo-release |
8 | A check for this skill |
rule-process-npm-release |
8 | A check for this skill |
rule-process-uv-release |
8 | A check for this skill |
rule-styleguide-a11y |
10 | Every check |
rule-styleguide-comments |
4 | Every check |
rule-styleguide-css |
6 | Every check |
rule-styleguide-file-tree |
5 | Every check |
rule-styleguide-html |
2 | Every check |
rule-styleguide-javascript |
16 | Every check |
rule-styleguide-naming |
1 | Every check |
rule-styleguide-typescript |
4 | Every check |
rule-toolchain |
5 | A check for this skill |
rule-toolchain-pnpm |
4 | Every check |
scaffold |
8 | Generated project scaffolding |
semantic |
1 | A semantic check with a decision endpoint configured |
rule-backend
| Rule | Label | What it rejects | Also in |
|---|---|---|---|
cors-explicit |
CORS configured with explicit origins | CORS must not use wildcard origin (*). Specify explicit allowed origins. | - |
error-exposure |
No internal error details in responses | Production error responses must not expose stack traces or internal error messages. | - |
helmet-headers |
Security headers via helmet | HTTP security headers should be set via helmet or framework equivalent. | - |
input-validation |
Input validation present | Request body and params must be validated (Zod, Joi, class-validator). | - |
rate-limiting |
Rate limiting configured | API endpoints should have rate limiting enabled. | - |
sql-injection |
No SQL injection via string concatenation | All SQL queries must use parameterized placeholders ($1, ?, :name). No string concatenation with user input. | - |
rule-data
| Rule | Label | What it rejects | Also in |
|---|---|---|---|
connection-pool |
Connection pooling | Database clients should use connection pooling with explicit max pool size. | - |
migration-safety |
Safe DDL operations | DDL must use IF NOT EXISTS / IF EXISTS for idempotent migrations. | - |
parameterized-queries |
Parameterized database queries | Queries must use placeholders ($1, ?, :name), not string concatenation. | - |
transaction-safety |
Multi-statement transactions | Multiple mutations should be wrapped in a transaction. | - |
rule-devops
| Rule | Label | What it rejects | Also in |
|---|---|---|---|
env-gitignore |
.env in .gitignore | .env files must be in .gitignore to prevent committing secrets. | - |
secret-detection |
No hardcoded secrets | Passwords, API keys, and tokens must use environment variables. | - |
rule-devops-docker
| Rule | Label | What it rejects | Also in |
|---|---|---|---|
docker-healthcheck |
HEALTHCHECK defined | Production images should include a HEALTHCHECK. | - |
docker-non-root |
Container runs as non-root user | Docker containers must switch to a non-root user. | - |
rule-frontend-angular
| Rule | Label | What it rejects | Also in |
|---|---|---|---|
angular-no-ngclass |
No ngClass directive | Angular templates must use [attr.data-part] instead of [ngClass]. | - |
rule-frontend-react
| Rule | Label | What it rejects | Also in |
|---|---|---|---|
react-hooks-rules |
React Hooks rules | Hooks must be called at top level, not inside conditions, loops, or callbacks. | - |
react-no-classname |
No className, use data-part | React components must use data-part instead of className for styling hooks. | - |
rule-frontend-svelte
| Rule | Label | What it rejects | Also in |
|---|---|---|---|
svelte-no-class-directive |
No class: directives | Svelte should use data-part attributes, not class:name directives. | - |
rule-frontend-vue
| Rule | Label | What it rejects | Also in |
|---|---|---|---|
vue-no-class-binding |
No :class or v-bind:class | Vue templates must use data-part instead of :class bindings. | - |
rule-process-adr
| Rule | Label | What it rejects | Also in |
|---|---|---|---|
adr-file-naming |
The record file is NNN-kebab-slug.md | The index derives its link from the file name, so a capital or an underscore breaks the link. | - |
adr-status-vocabulary |
The status is one of four values | A free-form status cannot be read by a later check, and a supersede with no successor stops the chain. | - |
adr-author-line |
The author is a person or a team | A tool name as author leaves nobody to ask when the decision is wrong. | - |
adr-ends-when |
The record says what would end it | A decision with no stated end condition is never revisited. | - |
adr-index-derived |
The index lists every record and nothing else | The index is derived from the directory, so a drift means somebody edited it by hand. | - |
adr-english |
The record is English | The tool refuses a non-English write, so the rule explains a record that arrived another way. | - |
rule-process-ai
| Rule | Label | What it rejects | Also in |
|---|---|---|---|
ai-english-only-filenames |
English-only file and folder names | Every file and folder name is English. A non-ASCII letter or a foreign word in any path segment fails this rule (see rule-styleguide-english). | - |
ai-english-only-prose |
English-only prose | Every generated comment, identifier and document is English. A non-ASCII letter in a comment, two distinct foreign function words in a comment, a foreign function word in an identifier, or non-ASCII/foreign prose in a .md document fail this rule (see rule-styleguide-english). | - |
ai-american-english |
American English spelling | The house style is American English: -or over -our, -ize over -ise, -er over -re, -og over -ogue. | - |
ai-no-emdash |
No em or en dashes | Prose uses ASCII punctuation. An em dash becomes a comma; an en dash becomes a hyphen. | - |
ai-no-typographic-chars |
No curly quotes or ellipsis glyphs | Curly quotes and the ellipsis character do not survive a copy-paste into source. Use straight quotes and three dots. | - |
ai-bidi-controls |
No invisible bidi control characters | Directional overrides and isolates reorder what a reader sees, so they are removed and reported. | - |
ai-no-emoji |
No emoji in prose documents | An emoji in a document is a tell and a compatibility risk; interface copy in code is out of scope. | - |
ai-nfc-text |
Text is NFC-normalized | A decomposed accent is a different byte sequence for the same letter, so diffs and greps miss it. | - |
ai-plain-sentence-length |
No sentence past 25 words | A descriptive sentence past 25 words is where a reader loses the thread; STE100 caps it there. | - |
ai-repeated-opener |
No three paragraphs opening the same way | Three paragraphs that start with the same word read as a template. | - |
ai-structural-tells |
The closing paragraph does not restate the opening | An opening and a closing that share most of their words say one thing twice. | - |
ai-box-rectangular |
A drawn box is a rectangle | Every line of a box ends in the same column. A right edge that steps in and out is the one tell a spell-check cannot see. | - |
ai-no-placeholder-implementation |
No placeholder or unfinished implementation | Generated code must not contain TODO/FIXME markers, placeholder comments, or not-implemented throws. | - |
ai-no-speculative-audit-table |
No audit/history table without a consumer | A new audit/history/log/event/archive table that nothing else in the change reads or writes is speculative structure. Covers SQL CREATE TABLE and Prisma model blocks, in snake_case or PascalCase. Add it when a consumer exists. |
- |
ai-no-suppressed-errors |
No suppressed type, lint, or runtime errors | Generated code must not silence errors with ignore directives or empty catch blocks. | - |
rule-process-cargo-release
| Rule | Label | What it rejects | Also in |
|---|---|---|---|
release-no-long-lived-token |
No long-lived crates.io token in a release workflow | crates.io supports Trusted Publishing: an OIDC exchange returns a token that expires in 30 minutes and the action that fetches it revokes it when the job ends. A workflow that still reads CARGO_REGISTRY_TOKEN from secrets. holds a credential that can publish any version of any crate its owner can reach, and it survives the release - which is the one thing the exchange removes. |
- |
release-token-step-scoped |
The publish token is written on the publish step | cargo cannot perform the OIDC exchange itself - cargo publish --help has no token or OIDC flag - so the temporary credential is injected through the environment. Where it is written decides who can read it: on the publish step it exists for that one command, at job level every step inherits it, and at workflow level every job does. The exchange buys nothing if the token is then handed to a build script. |
- |
release-actions-pinned |
Every action is pinned to a commit SHA | A release workflow runs with privileges nothing else in the repository has, and a tag like @v4 is a pointer its owner can move at any time without a diff here. Pinning the full SHA makes the update explicit and reviewable; the trailing version comment keeps it readable, so the pin is not a hex blob nobody dares touch. |
rule-process-npm-release, rule-process-uv-release |
release-id-token-isolated |
The publish token lives in one job that does not build | id-token: write is what lets a job ask crates.io for a short-lived publish token, and any code that job runs can ask for one too. A job that also builds or fetches dependencies hands that power to every build script in the workspace - and a Rust workspace has a lot of them. One job, no toolchain install, no cache: the exchange and the publish, nothing else. |
rule-process-npm-release, rule-process-uv-release |
release-dry-run-first |
A dry run runs before the upload | A publish is permanent: the version cannot be overwritten and the code cannot be deleted. cargo publish --dry-run (or cargo package, which is the same three steps) builds the archive, verifies it compiles and leaves it in target/package, so a crate that fails to package fails before the version exists. Without it, the first time anyone sees the contents is after the upload. |
- |
release-package-list |
The packed file list is inspected before the upload | cargo package --list is the only view of what the .crate will contain, and it is the step people skip. A crate that packs test fixtures, a target/ remnant or a large asset directory either fails at upload - crates.io refuses anything over 10 MB - or ships something nobody meant to publish, and both are permanent. |
- |
release-manifest-metadata |
The manifest carries the metadata crates.io requires | crates.io refuses a first publish without license (or license-file), description, repository and readme, and it refuses it after the archive is built. The failure is cheap but it arrives at the worst moment, and the missing field is usually the one that makes a crate discoverable at all - which is the difference between a release and a release somebody can find. |
- |
release-version-not-in-workflow |
The version is not written in the workflow | A version baked into a tag-triggered workflow is a second source of truth: the tag says one thing, the step writes another, and whichever lands second wins silently. The version belongs in Cargo.toml (or [workspace.package]), bumped by cargo-release, release-plz or a human in the commit the tag points at - never rewritten by the release pipeline that is supposed to ship it. |
rule-process-uv-release |
rule-process-npm-release
| Rule | Label | What it rejects | Also in |
|---|---|---|---|
release-no-npm-token |
No npm token in a release workflow | A token in the workflow is the whole supply chain attack surface of an npm release: it can publish any version of any package its owner can reach, it survives the release, and it is the one secret a compromised dependency is hunting for. Trusted Publishing removes the reason to hold it - npm mints a short-lived credential from the workflow's OIDC identity - so a workflow that still carries one is a workflow that has not finished the migration. | - |
release-stage-publish |
Publish through staging, not directly | Staged publishing turns one irreversible step into two: npm stage publish uploads the tarball where nobody can install it, and the stage is approved separately before it becomes a version. A workflow that calls npm publish has already given up that window - by the time anyone reads the run, the version exists. |
- |
release-actions-pinned |
Every action is pinned to a commit SHA | A release workflow runs with privileges nothing else in the repository has, and a tag like @v4 is a pointer its owner can move at any time without a diff here. Pinning the full SHA makes the update explicit and reviewable; the trailing version comment keeps it readable, so the pin is not a hex blob nobody dares touch. |
rule-process-cargo-release, rule-process-uv-release |
release-id-token-isolated |
The publish token lives in one job that does not install | id-token: write is what lets a job ask npm for a short-lived publish token, and any code that job runs can ask for one too. A job that also installs dependencies hands that power to every install script in the tree, and a job that shares the permission with another job hands it over twice. One job, no install, no cache: the minting step and the publishing step, nothing else. |
rule-process-cargo-release, rule-process-uv-release |
release-tag-trigger |
Releases are triggered by a tag | A release workflow with no tag filter turns every push to the default branch into a publish attempt. The tag is the human decision that says this commit is the version; when the trigger drops it, the version becomes a side effect of a merge nobody described as a release. | - |
release-ignore-scripts |
Installs and publishes pass --ignore-scripts | A dependency's install script runs on the machine that is about to publish, with whatever the publish job can reach. The release job is the one place where that trade is never worth it: nothing it installs needs a script, and the flag turns every compromised package with a postinstall into a failed install instead of an incident. |
- |
release-no-persist-credentials |
Checkout keeps no credentials | actions/checkout writes the job's token into the clone by default, and every later step in that job can push with it. A release job needs to read the repository, not write to it, so the credential is a capability nobody asked for - and the publish job is exactly where a leaked one costs the most. |
- |
release-least-privilege |
No write permission beyond the OIDC token | A workflow with no permissions block gets the repository default, which is often read-write on contents - enough to push a commit, move a tag or edit a release from a job that was only supposed to upload a tarball. The publish job needs one permission, id-token: write, and it needs nothing else, so a second write grant is a capability nobody wrote down. |
rule-process-uv-release |
rule-process-uv-release
| Rule | Label | What it rejects | Also in |
|---|---|---|---|
release-no-pypi-token |
No PyPI token in a release workflow | A PyPI token can upload any version of any project its owner can reach, it survives the release, and it is the first secret a compromised dependency looks for. Trusted Publishing removes the reason to hold one - uv exchanges the workflow's OIDC identity for a short-lived credential and revokes it afterwards, even when the upload fails - so a workflow that still carries a token has not finished the migration. | - |
release-build-no-sources |
The release build passes --no-sources | uv build honours tool.uv.sources, so a wheel built in a repository where a dependency points at a path or a git checkout bakes that substitution into the metadata. Consumers installing the sdist with pip or build get a package that cannot resolve, and nobody sees it until the release is out. --no-sources is the documented publish recommendation because it builds the package the way every other tool will. |
- |
release-actions-pinned |
Every action is pinned to a commit SHA | A release workflow runs with privileges nothing else in the repository has, and a tag like @v4 is a pointer its owner can move at any time without a diff here. Pinning the full SHA makes the update explicit and reviewable; the trailing version comment keeps it readable, so the pin is not a hex blob nobody dares touch. |
rule-process-cargo-release, rule-process-npm-release |
release-id-token-isolated |
The publish token lives in one job that does not install | id-token: write is what lets a job ask PyPI for a short-lived publish token, and any code that job runs can ask for one too. A job that also syncs dependencies hands that power to every build hook in the tree, and a job that shares the permission with another job hands it over twice. One job, no sync, no cache: the publish step and nothing else that resolves a dependency. |
rule-process-cargo-release, rule-process-npm-release |
release-sync-locked |
CI syncs the lockfile it committed | uv sync without --locked re-resolves the dependency tree on the machine that is about to build the release, so the artifact can carry a version of a package that exists in no commit. Whatever the lockfile says is what the release should ship; --locked turns a drift into a failed run instead of a wheel nobody can reproduce. |
- |
release-version-not-in-workflow |
The version is not written in the workflow | A version baked into a tag-triggered workflow is a second source of truth: the tag says one thing, the step writes another, and whichever lands second wins silently. Either the version lives in pyproject.toml and the workflow only reads it, or it is bumped in the same commit the tag points at - a literal in the workflow is the case where nobody agrees. |
rule-process-cargo-release |
release-attestations |
Provenance is produced, not assumed | uv publish uploads an attestation it finds beside an artifact and creates none - the docs say so, and a pipeline that assumes otherwise ships a release with no provenance while believing it has one. The rule fires on the gap: the workflow talks about attestations and no step produces them. |
- |
release-least-privilege |
No write permission beyond the OIDC token | A workflow with no permissions block gets the repository default, which is often read-write on contents - enough to push a commit or move the tag the release was triggered by. The publish job needs one permission, id-token: write, and it needs nothing else, so a second write grant is a capability nobody wrote down. |
rule-process-npm-release |
rule-styleguide-a11y
| Rule | Label | What it rejects | Also in |
|---|---|---|---|
a11y-aria-hidden-focusable |
Nothing focusable is aria-hidden | aria-hidden on a focusable element leaves it in the tab order while removing it from the accessibility tree, so focus lands on nothing announceable. | - |
a11y-focus-visible |
Focus stays visible | Removing the outline without replacing it leaves keyboard users with no way to see where they are. | - |
a11y-form-label |
Inputs are labeled | An input with no label, aria-label or aria-labelledby is announced by its type alone, so the user hears "edit text" and nothing else. | - |
a11y-html-lang |
The page declares its language | Without lang, a screen reader picks a voice by guess and pronounces the page in the wrong language. | - |
a11y-iframe-title |
Frames are named | An iframe with no title is announced as "frame", which tells the user nothing about whether to enter it. | - |
a11y-img-alt |
Images carry a text alternative | Every img needs alt. An empty alt is a valid answer for decoration; a missing one is not an answer at all. | - |
a11y-no-autoplay |
Media does not autoplay with sound | Audio starting on load competes with a screen reader and cannot be stopped by someone who has not found the control yet. | - |
a11y-positive-tabindex |
No positive tabindex | A positive tabindex takes the element out of document order, so focus order stops matching reading order. | - |
a11y-test-required |
Playwright + axe-core a11y tests | Browser tests require Playwright, AxeBuilder and a violation assertion. | - |
a11y-video-captions |
Video offers captions | A video element with no captions track excludes anyone who cannot hear it, and anyone in a room where sound is not an option. | - |
rule-styleguide-comments
| Rule | Label | What it rejects | Also in |
|---|---|---|---|
no-commented-out-code |
No implementations parked in comments | Three or more consecutive commented-out lines of code are a parked implementation. Delete them; git keeps the history. | - |
no-future-use-code |
No code kept for an imagined future | Markers like "for future use", "in case we need", or "just in case" mark code written for a requirement that does not exist yet. | - |
js-comment-no-history |
Comments say what the code does, never what it used to do | A comment describes the current behavior and the reason for it. The story of how the code got here belongs to the ADR log and to git, and a copy in a comment is a second record that goes stale. | - |
jsdoc-required |
JSDoc on every exported symbol | Every JS/TS file must have JSDoc on all exported functions, classes, constants, and types. Module-level JSDoc recommended. | - |
rule-styleguide-css
| Rule | Label | What it rejects | Also in |
|---|---|---|---|
css-comments-required |
CSS file-level comments | Every CSS file must start with a file-level comment describing its purpose, scope, and usage. Sections should be separated by comment headers. | - |
css-flex-first |
Flex first, grid only when flex cannot do it | display:grid has to earn itself: named template areas, subgrid, explicit item placement or overlap, auto-fit/auto-fill tracks, or a stated /* grid: why */ reason. Anything a flex row or column can do is flex. |
- |
css-no-ellipsis |
Prose wraps, it is never truncated | A label is cut with text-overflow: ellipsis, or clamped with -webkit-line-clamp, and its end disappears. The reader cannot identify a name it cannot read, and the width a truncation saves is width the layout already has. Wrap instead. | - |
external-css |
External CSS via adoptedStyleSheets or framework-native import | CSS must be loaded as external file. Use fetch->CSSStyleSheet.replaceSync->adoptedStyleSheets for vanilla, or framework-native imports (Angular styleUrls, Svelte <style>, CSS Modules or plain import for React/Solid). |
- |
no-css-classes |
Zero CSS classes in stylesheets | CSS must use only element selectors + [data-part] + [data-*] attribute selectors. No .class selectors (including pseudo-classes and combinators). | - |
no-inline-styles |
Zero inline styles in templates | Templates must not use style="..." attributes or JSX style={{...}} objects. All styling in external CSS files. | - |
rule-styleguide-file-tree
| Rule | Label | What it rejects | Also in |
|---|---|---|---|
barrel-exports-match-files |
Barrel exports match actual component files | The barrel file's export statements must reference existing component files with the correct _PascalCase names. | - |
barrel-file-tree |
Barrel pattern (index.js) | Every component folder must have an index.js barrel that re-exports the public API. | - |
component-export-check |
Component file exports a class or function | _PascalCase.js component files must export at least one class or function. Store/data files (_store.js) are exempt. | - |
component-file-prefix |
Component file has _PascalCase prefix | Component files inside component folders must start with an underscore and PascalCase: _Button.jsx, _Card.vue, _Modal.svelte. | - |
component-folder-barrel |
Component folder has index.js barrel file | Every component folder must have an index.js (or index.ts) that re-exports the public API. The barrel must contain at least one export statement. | - |
rule-styleguide-html
| Rule | Label | What it rejects | Also in |
|---|---|---|---|
data-part-naming |
data-part attribute for child elements | Component child elements with semantic roles must use data-part="role" attributes. | - |
semantic-html |
Semantic HTML mandatory | No <div> where <section>, <article>, <header>, <footer>, <nav>, <main>, <aside>, <form>, <ul>, <ol>, <li>, <search> exists. |
- |
rule-styleguide-javascript
| Rule | Label | What it rejects | Also in |
|---|---|---|---|
js-array-flat-map |
prefer flatMap over map().flat() | rows.map(fn).flat() allocates an intermediate array; use rows.flatMap(fn) instead. Only .flat() with no depth argument is flagged. | - |
js-blank-line-before-return |
Blank line before return statements | Every return statement must have a blank line before it (unless first in block). | - |
js-blank-line-between-methods |
Blank line between class methods | Every pair of class methods must have a blank line between them. | - |
js-blank-lines-blocks |
Multi-line blocks have blank lines above/below | if/for/while/switch/try/return/promise/method chains spanning >1 line must have a blank line above and below. | - |
js-const-top |
Variable declarations grouped at top | const/let/var declarations should be grouped at the top of their scope, separated from code by a blank line. | - |
js-for-loop-braces |
for loops must have braces and be multi-line | Every for/for...of/for...in body must use {} and span multiple lines. No for (...) stmt; | - |
js-if-braces |
if/else always use braces | Every if, else, else if must use {} braces. Single-line if without braces is forbidden. | - |
js-new-for-builtins |
new required for Map/Set/Date/Array/WeakMap/WeakSet | Builtin constructors Map, Set, Date, Array, WeakMap, WeakSet must be called with new. Calling them as functions (const m = Map()) is forbidden. String, Number, Boolean, Symbol, BigInt are exempt. | - |
js-no-array-for-each |
for...of over Array.forEach() | Prefer for...of loops over Array.prototype.forEach: better type narrowing, direct await, and break/continue support. | - |
js-no-blank-lines-in-blocks |
No blank line immediately after { or before } | Blocks must not have blank lines at start or end: { code -> forbidden. code } -> forbidden. | - |
js-no-inline-blocks |
Blocks must be multi-line (no inline { code }) | All { } blocks (if/for/while/arrow functions/promises) must span multiple lines. No inline { doThing(); }. | - |
js-node-protocol |
node: protocol for Node builtins | Node builtin imports must use the explicit node: prefix (import fs from 'node:fs'). The bare form can be shadowed by a same-named npm package. | - |
js-prefer-replace-all |
replaceAll over replace with /g regex | str.replace(/x/g, y) with a global regex literal should be str.replaceAll('x', y). | - |
js-prefer-set-has |
Set.has() over Array.includes() inside loops | Array.prototype.includes() inside a loop or iteration callback (for/filter/map/some/every/find) is O(n) per call - O(n²) total. Build a Set once and use Set.prototype.has() for O(1) membership. | - |
js-single-line-method |
Class method bodies must be multi-line | Every method body must span multiple lines. No method() { stmt; } on one line. | - |
js-throw-new-error |
throw requires new for error constructors | Errors must be created with new: throw new Error('...'). Calling error constructors as functions (throw Error('...')) is forbidden. | - |
rule-styleguide-naming
| Rule | Label | What it rejects | Also in |
|---|---|---|---|
file-naming |
File naming convention (_PascalCase.js) | Component files inside folders must be _PascalCase.js. Utils are _kebab-case.js. | - |
rule-styleguide-typescript
| Rule | Label | What it rejects | Also in |
|---|---|---|---|
ts-catch-unknown |
Catch clauses use unknown | JavaScript can throw anything - a string, null, an object that is not an Error. any lets the handler assume otherwise and crash. | - |
ts-no-double-assertion |
No as-unknown-as escape hatch | A double assertion silences the compiler twice over: it defeats both the original type and the check that would have caught the mismatch. | - |
ts-no-explicit-any |
No explicit any | any turns off checking exactly where a value is least understood. Use unknown and narrow it. | - |
ts-union-over-enum |
Union types over enum | An enum emits runtime code and numeric enums reverse-map. A string union compiles to nothing and autocompletes the same. | - |
rule-toolchain
| Rule | Label | What it rejects | Also in |
|---|---|---|---|
bundler-no-public-secret |
No secret in a public variable | A public build variable reaches the browser, so its name must not look like a secret. | - |
bundler-dev-not-exposed |
Dev server not exposed | A dev server on every interface, or with an all-hosts allowlist, exposes the machine and the project. | - |
bundler-target-declared |
Browser target declared | A build names the browsers it must run in, so the default does not decide silently. | - |
bundler-io-declared |
Entry and output declared | A build states where it starts and where it writes, so the result does not depend on a convention nobody wrote. | - |
bundler-sourcemap-policy |
Source map policy | Source maps in a production build expose the source, so turning them on is a decision with a reason. | - |
rule-toolchain-pnpm
| Rule | Label | What it rejects | Also in |
|---|---|---|---|
pkg-exact-versions |
Dependencies are pinned exactly | Every dependency, devDependency and optionalDependency is an exact version: no ^, ~, comparator, alternation, wildcard, x-range or dist-tag. A range leaves the subdependency tree free to change between two installs of the same commit. peerDependencies are exempt: a peer range states compatibility, it does not install anything. | - |
pkg-no-corepack |
No corepack activation fields | No packageManager field and no devEngines.packageManager. Both hand control of the install to corepack, which can then refuse it - offline, behind a proxy, or shadowing the package manager already installed. engines is the sibling rule, pkg-no-engines. |
- |
pkg-no-engines |
No engines field | engines is a declaration a package manager acts on: npm, pnpm and yarn warn or refuse when the running Node falls outside the range, so the field gets a vote on whether the install runs - the same veto corepack gets, reached another way. The version belongs in .nvmrc and in the installer, where a human reads it and nothing enforces it in the middle. |
- |
verified-dependencies-only |
Only manifest-verified dependencies | When allowedDependencies context is provided, every external import must exist in that manifest-derived list. | - |
scaffold
| Rule | Label | What it rejects | Also in |
|---|---|---|---|
scaffold-prettierrc |
.prettierrc present | Project must have .prettierrc file. | - |
scaffold-nvmrc |
.nvmrc present | Project must have .nvmrc file. | - |
scaffold-npmrc |
.npmrc present | Project must have .npmrc file. | - |
scaffold-editorconfig |
.editorconfig present | Project must have .editorconfig file. | - |
scaffold-gitignore |
.gitignore present | Project must have .gitignore file. | - |
scaffold-eslint.config.mjs |
eslint.config.mjs present | Project must have eslint.config.mjs file. | - |
scaffold-tsconfig.json |
tsconfig.json present | Project must have tsconfig.json file. | - |
scaffold-jsconfig.json |
jsconfig.json present | Project must have jsconfig.json file. | - |
semantic
| Rule | Label | What it rejects | Also in |
|---|---|---|---|
semantic-error-message-actionable |
Error messages tell the caller what to do | A thrown error names the next action, not only the failure (ADR 147; docs/audits/decision-models.md). | - |