Skip to content
ƒtsforgev0.52.0
19

Meta-rules

5 min read

Most tsforge rules lint .ts files through ESLint. Meta-rules are different: they inspect project files that normal lint does not cover.

Think of them as housekeeping checks. Is package.json pinned correctly? Is tsconfig.json strict? Does every logic module have a colocated test file? Are GitHub Actions pinned with explicit permissions?

They run in the same gate pass as ESLint. A meta-rule failure blocks the task the same way a type error would.

Tune meta-rule severity in tsforge.config.json the same way as ESLint rules. The strict profile elevates key supply-chain and CI checks to error by default.

AreaExample checks
package.jsonexact version pins, lockfile present, packageManager field, no git/tarball deps
tsconfig.jsonstrict mode on, recommended flags, include paths exist
Source textno @ts-ignore, no eslint-disable comments
CI workflowspinned actions/runners, timeouts, explicit permissions, no unsafe pull_request_target
Dockerfilespinned base image, non-root USER, no secret literals in ENV/ARG
Next.js configno wildcard image hostnames, instrumentation present
Testsservice/util files have a matching *.test.ts sibling
Drizzle (when pack active)migrations checked in, no drizzle-kit push in CI
IDChecks
package-exact-depsno ^/~ ranges in dependencies
no-overlapping-libsforbid redundant HTTP client libraries
fastify-security-pluginshelmet/cors/rate-limit when using Fastify
lockfile-requiredexactly one lockfile for the detected package manager
single-package-managerno mixed lockfiles
package-manager-field-requiredpackageManager in package.json
no-git-or-tarball-dependencieswarn on git+ / HTTP tarball URLs
no-undeclared-dependenciesevery imported package is declared in package.json (no relying on hoisting)
dependency-overrides-require-commentoverrides/resolutions need an adjacent comment
production-must-not-use-drizzle-pushno drizzle-kit push in scripts/CI (Drizzle projects)
migrations-must-be-checked-indrizzle/ or migrations/ folder exists (Drizzle projects)
IDChecks
no-eslint-disable-commentsno inline disables
no-ts-suppressionsno @ts-ignore / @ts-expect-error / @ts-nocheck

Both scan every hand-written .ts/.tsx under src/, tests/, and scripts/. Generated *.gen.ts files are skipped. Codegen output (e.g. TanStack’s route tree) legitimately ships with /* eslint-disable */ + @ts-nocheck, and the model can’t write *.gen.ts anyway (it’s vendored/read-only), so the ban stays airtight everywhere the model actually writes.

IDChecks
tsconfig-paths-existinclude paths exist on disk
tsconfig-strictstrict mode on
tsconfig-recommended-flagsuseUnknownInCatchVariables, erasableSyntaxOnly, exactOptionalPropertyTypes, and related flags
next-proxy-over-middlewaremigrate middleware.tsproxy.ts (Next.js 16)
next-instrumentation-presentinstrumentation.ts with OpenTelemetry (Next.js apps)
next-image-remote-patterns-no-wildcardsno ** hostnames in remotePatterns
IDChecks
test-sibling-requireda logic file (one exporting a function/class) the agent changes has a test: co-located *.test.ts or mirrored under tests/. Error when TDD mode is on (the default), warn when off. Scoped to changed files, so it never blocks on pre-existing untested code.
IDChecks
workflow-actions-pinnedpinned action refs (tag or SHA)
workflow-runner-pinnedno ubuntu-latest
workflow-timeout-requiredjob timeouts set
workflow-permissions-explicitevery workflow declares permissions:
workflow-permissions-least-privilegewarn on broad contents: write / id-token: write
no-pull-request-target-untrusted-checkoutunsafe pull_request_target + PR head checkout
no-github-context-in-shell${{ github.event in run: without env indirection

Per-file ESLint can’t see the module graph; this builds it from the project’s own relative imports.

IDChecks
no-circular-importsno import cycle (A → B → A). They cause partial-initialization bugs and defeat tree-shaking

Active whenever a Dockerfile (Dockerfile, Dockerfile.*, or *.Dockerfile) exists at the project root or one level down. No-op otherwise.

IDChecks
dockerfile-base-image-pinnedevery FROM pins an explicit non-latest tag (or @sha256: digest); build-stage refs and scratch are exempt
dockerfile-non-root-usera non-root USER is declared so the container does not run as root
dockerfile-no-secrets-in-env-argno secret-looking ENV/ARG literal (*_KEY, *_TOKEN, *_SECRET, *_PASSWORD): inject at runtime
LevelEffect
errorGate fails. Model must fix it.
warnReported but gate can still pass.
offSilenced via tsforge.config.json.

Full list with descriptions: Rule catalog (meta-rules section at the bottom).

Big picture · Rule packs · How tsforge builds the gate