Skip to content
BoringStack
Star

lint:meta rules

1 min read

ESLint enforces architecture inside TypeScript modules. lint:meta catches repo-level drift ESLint cannot see: unpinned GitHub Actions, env cascade gaps, forbidden inline disables, cross-repo imports, and missing test siblings. It runs inside bun run validate.

typecheck → ESLint → lint:meta → knip → tests
ui-template · supply-chain
Rule ID
CI-critical
What it guards
package-json-exact-deps
no
dependencies and devDependencies must use exact versions; peerDependencies must use caret (^).
no-overlapping-libs
no
package.json must not list forbidden overlapping library pairs.
ui-template · ci
Rule ID
CI-critical
What it guards
github-actions-permissions
no
GitHub Actions workflows require permissions block and SHA-pinned uses: refs.
github-actions-permissions:verify
no
Pinned action SHAs resolve on github.com (lint:meta:verify only).
github-actions-timeout-required
no
GitHub Actions jobs require an explicit timeout-minutes (reusable-workflow calls exempt).
github-actions-bun-cache
no
Workflows running bun install must cache ~/.bun/install/cache.
github-actions-concurrency-explicit
no
Workflows with a concurrency block must set cancel-in-progress explicitly.
github-actions-expression-syntax
no
Every expression opener in a workflow must be a well-formed Actions expression.
github-actions-service-image-digest-pin
no
Workflow service/container images must be pinned by @sha256 digest, not tag alone.
pre-push-ci-parity
no
CI workflow must include every command listed in scripts/ci/pre-push.manifest.json.
tofu-bootstrap-hardening
no
infra/bootstrap must keep its hardening invariants: server lifecycle guard, no world-open variable defaults, no curl-pipe-sh, version-pinned required_providers.
engine-pin-parity
no
Node and Bun version pins must stay aligned across .nvmrc, package.json, Docker, and CI.
dockerfile-base-image-sha-pin
no
Dockerfile base images must be pinned by @sha256 digest, not tag alone.
ui-template · env
Rule ID
CI-critical
What it guards
env-cascade-drift
no
Vite env keys must align across schema.ts, .env.example, and vite-env.d.ts.
env-no-direct-import-meta-env
no
Single entry point for env: every source file outside env.loader.ts must import the typed `env` object instead of reading `import.meta.env` directly.
ui-template · artifacts
Rule ID
CI-critical
What it guards
generated-artifact-contract
no
Generated ACL types and OpenAPI schema files must exist with required banner text.
modulepreload-size-limit-coverage
no
.size-limit.json must include globs for all modulepreload entry chunks.
ui-template · source-text
Rule ID
CI-critical
What it guards
canonical-helpers-single-home
no
Helpers in the canonical registry must only be declared in their single source-of-truth file.
docs-no-retired-credentials
no
Documentation prose must not reference retired default credentials.
i18n-locale-keys-used
no
Locale keys defined in en/*.json must be referenced in src (dynamic t() prefixes exempt).
forbidden-text
no
Source files must not contain inline lint/TS suppressions, raw HTML, direct env access, raw fetch, or banned Tailwind dark-mode variant classes.
no-inline-lint-disable
no
Inline ESLint disables are not allowed.
no-ts-ignore
no
TypeScript suppression comments are not allowed.
no-dangerous-html
no
Raw HTML rendering requires a dedicated sanitizer and security review.
env-access
no
Read Vite env through src/lib/env only.
no-raw-fetch
no
Use the typed apiClient; raw fetch is restricted to src/lib/api/openapi.
no-inline-object-cast
no
Casting to an inline object type (`as { … }`) skips validation.
no-sleep-in-e2e
no
Wall-clock sleeps flake under CI load and tax every run.
no-hardcoded-iso-dates-in-fixtures
no
Hardcoded ISO timestamps in shared factories/e2e age out.
no-dark-variant
no
The `dark:` Tailwind variant is banned.
no-cross-repo-import
yes
Relative imports must stay inside apps/ui; no backend or infra source paths.
no-raw-role-literal
no
Use ROLE.* from acl.types instead of raw owner/admin/member/viewer string literals.
no-raw-fetch-scripts
no
Scripts must not call global fetch except github-actions-permissions.ts (lint:meta --verify SHA check).
queries-no-silent-error-swallow
no
*.queries.ts files must not silently swallow query errors as `null`. Let the typed error propagate so consumers can distinguish auth from outage; opt-out per-catch with `// allow-silent: <reason>` when an explicit null is genuinely the right contract.
ui-template · testing
Rule ID
CI-critical
What it guards
logic-files-require-test-sibling
no
Logic modules must ship with a colocated *.test.ts or *.test.tsx sibling.
lint-meta-rules-self-covered
no
Every lint-meta rule module must re-export its check function from cli.ts and carry a describe() test block — the guardrails must themselves be guarded.
test-files-require-source-sibling
no
Colocated test files must mirror a source sibling (no orphaned tests).
skipped-tests-need-tracking
no
Skipped tests (.skip/.only/xit/xdescribe) must carry an issue URL or TODO(@owner) so the debt has a tracked owner.
ui-template · config
Rule ID
CI-critical
What it guards
eslint-config-no-warn
no
ESLint severities must be "error" or "off", not "warn".
eslint-ban-type-assertions
no
ESLint must pin @typescript-eslint/consistent-type-assertions to assertionStyle "never"; disabling it requires an audited eslint-meta-allow-assertion-exemption marker.
tsconfig-include-paths-exist
no
Literal tsconfig include/files entries must point at files that exist (globs exempt); checks this app and sibling apps.
eslint-plugin-contract-parity
no
Every installed @boring-stack-pkg eslint plugin must appear in AGENT_CONTRACT.md, and vice versa.
api-template · supply-chain
Rule ID
CI-critical
What it guards
package-json-exact-deps
no
dependencies and devDependencies must use exact versions (no ranges).
no-overlapping-libs
no
package.json must not list forbidden overlapping library pairs.
package-override-parity
no
package.json overrides must be reflected in the app's own bun.lock, mirrored by sibling apps that resolve the same package, and each carry a `//overrides` entry documenting why it is pinned.
shared-tool-version-parity
no
Shared dev tooling (ESLint, TypeScript, Prettier, knip, …) must be pinned to the same version in every app that declares it.
api-template · ci
Rule ID
CI-critical
What it guards
github-actions-permissions
no
GitHub Actions workflows require permissions block and SHA-pinned uses: refs.
github-actions-permissions:verify
no
Pinned action SHAs resolve on github.com (lint:meta:verify only).
github-actions-timeout-required
no
GitHub Actions jobs require an explicit timeout-minutes (reusable-workflow calls exempt).
github-actions-bun-cache
no
Workflows running bun install must cache ~/.bun/install/cache.
github-actions-concurrency-explicit
no
Workflows with a concurrency block must set cancel-in-progress explicitly.
github-actions-paths-filter-parity
no
Workflows pairing push.paths with a dorny/paths-filter gate must keep the two path sets mutually covered.
github-actions-pip-install-pinned
no
Workflow pip install steps must pin package versions with == so CI tools cannot drift with PyPI releases.
github-actions-runner-pinned
no
Workflows must pin runner images to an explicit OS version instead of floating *-latest labels.
github-actions-security-no-cancel
no
Security scan workflows (*-security-{sast,secrets,deps}) must set concurrency cancel-in-progress: false so no pushed ref goes unscanned.
github-actions-expression-syntax
no
Every expression opener in a workflow must be a well-formed Actions expression.
github-actions-service-image-digest-pin
no
Workflow service/container images must be pinned by @sha256 digest, not tag alone.
pre-push-ci-parity
no
CI workflow must include every command listed in scripts/ci/pre-push.manifest.json.
security-scanner-version-parity
no
All security workflows must pin a single gitleaks version and a single semgrep image, and scripts/ci/pre-push-security.sh must carry a local-vs-CI version-parity check for every scanner the workflows pin.
tofu-bootstrap-hardening
no
infra/bootstrap must keep its hardening invariants: server lifecycle guard, no world-open variable defaults, no curl-pipe-sh, version-pinned required_providers.
engine-pin-parity
no
Bun version pin must stay aligned across package.json, Docker, and CI.
dockerfile-base-image-sha-pin
no
Dockerfile base images must be pinned by @sha256 digest, not tag alone.
api-template · env
Rule ID
CI-critical
What it guards
env-cascade-drift
no
TypeBox env schema keys must align with .env.example documentation.
env-no-direct-process-env
no
Single entry point for env: every source file outside validate.ts must import the typed `env` object instead of reading `process.env` directly.
api-template · artifacts
Rule ID
CI-critical
What it guards
generated-artifact-contract
no
Sibling apps/ui generated ACL and OpenAPI files must carry required banner text.
api-template · source-text
Rule ID
CI-critical
What it guards
forbidden-text
no
Source files must not contain inline lint/TS suppression comments.
no-inline-lint-disable
no
Inline ESLint disables are not allowed.
no-ts-ignore
no
TypeScript suppression comments are not allowed.
canonical-helpers-single-home
no
Helpers in the canonical registry must only be declared in their single source-of-truth file.
docs-no-retired-credentials
no
Documentation prose must not reference retired default credentials.
external-client-timeout
no
SDK clients (Stripe/OpenAI/Anthropic) need a timeout option; email transports (Resend/SendGrid/nodemailer) must be bounded; fetch() in src needs an AbortSignal.
no-raw-role-literal
no
Use ROLE.* from acl.constants.ts instead of raw owner/admin/member/viewer string literals.
schema-enum-field-consistency
no
A fixed-value field typed as t.Union([t.Literal(...)]) in one schema must not be t.String() in another schema in the same file — keep the generated API client precise.
audit-log-read-account-scoped
no
Queries filtering auditLog by userId must also reference auditLog.targetAccountId — userId-only reads bleed a multi-account user's events across tenant boundaries.
api-template · testing
Rule ID
CI-critical
What it guards
routes-require-test-sibling
no
Route modules must ship with a matching HTTP-level test under tests/api/.
logic-files-require-test-sibling
no
Logic modules must ship with a matching tests/**/*.test.ts sibling.
lint-meta-rules-self-covered
no
Every lint-meta rule module must re-export its check function from cli.ts and carry a describe() test block — the guardrails must themselves be guarded.
skipped-tests-need-tracking
no
Skipped tests (.skip/.only/xit/xdescribe) must carry an issue URL or TODO(@owner) so the debt has a tracked owner.
security-spec-no-silent-bail
no
security-spec tests must fail when a dependency is missing: no silent-bail guards (requireDb/requireValkey/isDbAvailable/isValkeyReachable) and no skipped cases.
security-spec-requires-control
no
Every describe block in security-spec must declare its own `control:` case that passes, so an expected failure is distinguishable from a broken fixture.
touch-tests-too
no
Modified logic/route files must include a matching test change (opt-in via LINT_META_TOUCHED_BASE).
api-template · config
Rule ID
CI-critical
What it guards
eslint-config-no-warn
no
ESLint severities must be "error" or "off", not "warn".
eslint-ban-type-assertions
no
ESLint must pin @typescript-eslint/consistent-type-assertions to assertionStyle "never"; disabling it requires an audited eslint-meta-allow-assertion-exemption marker.
eslint-override-paths-exist
no
Literal test-file paths in eslint.config.* overrides must exist on disk.
tsconfig-include-paths-exist
no
Literal tsconfig include/files entries must point at files that exist (globs exempt); checks this app and sibling apps.
eslint-plugin-contract-parity
no
Every installed @boring-stack-pkg eslint plugin must appear in AGENT_CONTRACT.md, and vice versa.

Implementation lives under scripts/lint-meta/ in each template.

  1. Implement IMetaRule under scripts/lint-meta/rules/<category>/
  2. Register it in scripts/lint-meta/registry.ts
  3. Run bun run generate:lint-meta-docs (ui) or bun run generate:lint-meta-docs (api)
  4. Run the boringstack docs generators and commit the updated JSON catalogs
  5. Add a test under tests/lint-meta/