@noctcore/lint-meta-rules
Whole-repo structure-lock rules for the @noctcore/harness lint-meta runner.
What it solves
Section titled “What it solves”Some invariants are not about one file: every workspace package is named <scope>/<dir>, every imported workspace package is a declared dependency, every GitHub Action is pinned to a commit SHA, no ESLint rule resolves to warn, no source file grows past a line cap. ESLint’s per-file model cannot see those. These rules can.
What it will yell at you about
Section titled “What it will yell at you about”This is not an ESLint plugin. Each rule is a pure function of a repo context returning violations, implementing the IMetaRule contract from @noctcore/harness, and each is a factory (createXRule(options)) so anything project-specific is an option with a default. The intended consumer is the harness export pipeline, which inlines rule source into a repo’s .nightcore/lint-meta/; nothing requires this package at runtime. It is a bad fit if you are not running the harness, and several defaults (the @nightcore scope, a 400-line cap) are starting points you are expected to override. The /i18n, /prisma and /resolved-config entry points load ESLint and are kept off the main entry for that reason. The /session and /trpc entry points hold fences around one seam each (the call that mints a session; routers and the clients that call them), and are inert until you name that seam’s methods and files.
Install and configure
Section titled “Install and configure”npm install --save-dev @noctcore/lint-meta-rules# or: bun add -D / pnpm add -D @noctcore/lint-meta-rulesimport { createAllRules, createPackageShapeRule } from '@noctcore/lint-meta-rules';
// Every catalog rule with default options:const rules = createAllRules();
// Or one rule, retargeted at your repo:const shape = createPackageShapeRule({ scope: '@acme' });A source catalog for the @noctcore/harness lint-meta runner, not an ESLint plugin. 28 rules.
Each rule links to its page, with the options it takes and Incorrect and Correct examples. A good first
read is github-actions-sha-pinned.
| Rule | What it enforces | Category | Entry point |
|---|---|---|---|
agents-doc-presence | AGENTS.md must exist at the repo root, every surface, and every non-opted-out package. | source-text | @noctcore/lint-meta-rules |
canonical-helpers-single-home | Pure helpers must live in one canonical home (flag the same exported symbol appearing in multiple homes). | source-text | @noctcore/lint-meta-rules |
dockerfile-base-image-digest-pin | Dockerfile FROM base images must be pinned by @sha256: digest (scratch and earlier build stages exempt). | ci | @noctcore/lint-meta-rules |
eslint-config-no-warn | Every rule in the RESOLVED ESLint config is "error" or "off", never "warn", including severities a spread preset injects. | config | @noctcore/lint-meta-rules/resolved-config |
file-size-ratchet | Source files stay at or under 400 raw lines. Today's offenders are grandfathered by .nightcore/lint-meta/baselines/file-size-ratchet.json; a new/grown offender fails, and a stale/shrunk baseline entry demands tightening. | source-text | @noctcore/lint-meta-rules |
github-actions-least-privilege-permissions | GitHub Actions workflows declare a read-only top-level permissions: (no write-all/read-all, no <scope>: write); writes go on the job that needs them. | ci | @noctcore/lint-meta-rules |
github-actions-no-template-injection | GitHub Actions run: and github-script bodies never expand attacker-controllable ${{ }} context (issue/PR titles, comments, branch names); pass it through env: instead. | ci | @noctcore/lint-meta-rules |
github-actions-runner-pinned | GitHub Actions jobs must run on a pinned runner image (for example ubuntu-24.04), never a *-latest label. | ci | @noctcore/lint-meta-rules |
github-actions-sha-pinned | GitHub Actions uses: refs must be pinned to a 40-character commit SHA with a # vN comment (local ./ actions exempt). | ci | @noctcore/lint-meta-rules |
idempotency-key-parity | Procedures carrying the idempotency middleware must have a client caller that sends an idempotency key, or no client caller at all. | source-text | @noctcore/lint-meta-rules/trpc |
layer-rank | Fixed dependency direction by rank: a module imports only strictly-lower-ranked <scope> packages (equal/upward forbidden). | source-text | @noctcore/lint-meta-rules |
no-cloned-component-folders | A component folder name may exist under only ONE feature. Shared surfaces are hoisted; divergent ones get a divergent name. Today’s clones are frozen in a shrinking allowlist. | source-text | @noctcore/lint-meta-rules |
no-warn-severity | ESLint severity is 'error' or 'off', never 'warn'. A rule that matters is an error; a failure is fixed, not silenced. | config | @noctcore/lint-meta-rules |
package-shape | Every workspace is named <scope>/<dir>; library packages expose a barrel and point main/module/types/exports at the built output. | config | @noctcore/lint-meta-rules |
prisma-method-surface | The Prisma reads and writes the rules police partition the generated client's <Model>Delegate method surface exactly, so a Prisma upgrade cannot add an unguarded method. | config | @noctcore/lint-meta-rules/prisma |
security-scanner-version-parity | The gitleaks version pinned in the workflows must equal the one in scripts/ci/pre-push.sh, and the hook must compare a native gitleaks against it at run time. | ci | @noctcore/lint-meta-rules |
service-image-digest-pin | Workflow service and container images, and docker-compose images, must be pinned by @sha256: digest (a service that builds locally is exempt). | ci | @noctcore/lint-meta-rules |
session-epoch-captured | Every call into the sign-in seam must pass the session epoch captured before the credential was read, or a revocation landing during the credential check loses the race. | source-text | @noctcore/lint-meta-rules/session |
session-kind-stamped | Every call that mints a session must stamp the principal kind onto it, or sit in an allowlisted, provably single-kind flow; a session read without the kind falls back to a default and can silently promote one kind of account into another. | source-text | @noctcore/lint-meta-rules/session |
session-landing-declared | Every file that opens a door into a session must declare where it leaves the caller, and a door whose landing demands a return shape (the one that carries the principal kind to the client) must have it. | source-text | @noctcore/lint-meta-rules/session |
session-mint-callers | The method that mints a session may only be called from allowlisted files; a new sign-in entry point must route through the gate in front of it so the gate cannot be bypassed. | source-text | @noctcore/lint-meta-rules/session |
tenant-model-registry-parity | Every tenant-bearing Prisma model is scoped by the runtime extension or exempt with a reason, and the tenant lint rules resolve with exactly that registry. | config | @noctcore/lint-meta-rules/prisma |
test-runner-segregation | Bun-side packages use 'bun:test'; foreign-side packages use 'vitest'. Never mix runners. | testing | @noctcore/lint-meta-rules |
test-sibling-enforcement | Every source file matched by include must have a colocated sibling test. Pure helpers must ship a test. | source-text | @noctcore/lint-meta-rules |
test-workspace-enrollment | Every candidate package with test files must be enumerated in the root 'test:node' script. | testing | @noctcore/lint-meta-rules |
translation-dead-keys | Every translation catalog key must be reachable from the source: named by a translation call, or spelled by some string in the code. | source-text | @noctcore/lint-meta-rules/i18n |
ui-primitive-shape | A folder primitive under the ui root must ship its proof siblings (test, stories); a flat primitive must not carry sibling proof files at the root. | source-text | @noctcore/lint-meta-rules |
workspace-graph-parity | Imported <scope>/* specifiers must be declared workspace:* deps, and tsconfig references must mirror those deps. | config | @noctcore/lint-meta-rules |