npm package: @schafevormfenster/eslint-plugin
Description: This package contains **Custom ESLint Rules** that enforce logging, architectural, REST, testing, and other domain-specific patterns for the `@schafevormfenster` platform. It translates our internal guidelines into automated checks that run before code is
Latest version: 0.0.24

# @schafevormfenster/eslint-plugin

This package contains **Custom ESLint Rules** that enforce logging, architectural, REST, testing, and other domain-specific patterns for the `@schafevormfenster` platform. It translates our internal guidelines into automated checks that run before code is committed or deployed.

## Purpose

This custom ESLint plugin serves three key functions:

- **Enforcement**: Automates the verification of internal coding guides (e.g., "Always log before throwing," "Include required API route files").
- **Education**: Provides helpful error messages to guide developers towards correct patterns during development.
- **Safety**: Prevents common domain-specific mistakes (e.g., logging sensitive keys, missing cache headers in GET handlers).

## Integration

This plugin is designed to be used via **`@schafevormfenster/eslint-config`**, which provides pre-configured rule sets for different project types. The plugin integrates seamlessly into your development workflow, running checks during:

- Local development (IDE integration)
- Pre-commit hooks
- CI/CD pipelines

## Documentation Structure

This plugin provides two types of documentation:

### 📚 Coding Instructions (`docs/instructions/`)

Living guidelines for engineers and AI coding agents. These documents describe **how to write code** that complies with our architectural patterns:

- **[logging-guide.md](docs/instructions/logging-guide.md)** - Structured logging with pino
- **[rest-service-design-guide.md](docs/instructions/rest-service-design-guide.md)** - REST API design patterns
- **[client-service-coordinator-guide.md](docs/instructions/client-service-coordinator-guide.md)** - Layered architecture with clients, services, and coordinators
- **[caching-guide.md](docs/instructions/caching-guide.md)** - Caching strategies and limitations
- **[testing-standards-guide.md](docs/instructions/testing-standards-guide.md)** - Unified testing architecture and best practices
- **[component-test-guide.md](docs/instructions/component-test-guide.md)** - Component testing patterns for services and coordinators
- **[integration-test-guide.md](docs/instructions/integration-test-guide.md)** - Integration testing patterns for clients
- **[rest-api-e2e-testing-guide.md](docs/instructions/rest-api-e2e-testing-guide.md)** - E2E testing patterns
- **[unit-test-env-vars-guide.md](docs/instructions/unit-test-env-vars-guide.md)** - Environment variable handling in tests
- And more...

**For Engineers and AI Agents**: Consult these instruction documents as your primary coding guidelines. See [INSTRUCTIONS.md](INSTRUCTIONS.md) for details on how to use these guides.

**For Consuming Applications**: Maintain an `AGENTS.md` file in your project root that references these instruction documents so AI coding agents know to follow them before ESLint enforcement runs. See [INSTRUCTIONS.md](INSTRUCTIONS.md) for the recommended pattern.

### 📋 Rule Catalog (`docs/rules/`)

Technical documentation for each ESLint rule, including examples of correct and incorrect code:

- **[enforce-api-route-structure.md](docs/rules/enforce-api-route-structure.md)** - Required files for API routes
- **[enforce-log-before-throw.md](docs/rules/enforce-log-before-throw.md)** - Logging before throwing errors
- **[no-sensitive-log-keys.md](docs/rules/no-sensitive-log-keys.md)** - Prevent logging sensitive data
- And 21+ more rules...

See the [full rule catalog below](#rules) for a complete list organized by domain.

## Semantic Module Architecture

We use a **"Logical Monolith"** structure. All custom rules live in this single package but are organized into **semantic modules** (domains). You can enable rules for specific domains without enabling everything.

### Domains
- **Logging**: Enforces structured logging, pino usage, and error handling.
- **Architecture**: Enforces file naming conventions (kebab-case) and folder structures.
- **Caching**: Enforces correct caching headers and HTTP method usage.
- **REST**: Enforces API route structure, schema exports, and path conventions.
- **Stack**: Enforces technology choices and prevents forbidden imports.
- **Testing**: Enforces testing best practices (env vars, error matching, file naming, test tier separation).
  - **Testing-E2E**: Rules specific to end-to-end tests (Playwright).
  - **Testing-Unit**: Rules specific to unit tests (Vitest).
  - **Testing-Component**: Rules specific to component tests (Vitest, `*.component.test.ts`).
  - **Testing-Integration**: Rules specific to integration tests (Vitest, `*.integration.test.ts`).
  - **Testing-Coverage**: Rules for source files to enforce test coverage per architectural layer.
- **Security**: (Planned) Prevents usage of insecure patterns.
- **Queue**: (Planned) Enforces correct retry behavior for queue handlers.

## Usage

Typically, you will use this via `@schafevormfenster/eslint-config`. However, if you need to use it directly or want to understand the available modular configs:

```javascript
import svfPlugin from "@schafevormfenster/eslint-plugin";

export default [
  {
    plugins: {
      "@schafevormfenster": svfPlugin
    },
    // Option A: Use a preset (Recommended)
    ...svfPlugin.configs.logging.rules,

    // Option B: Configure manually
    rules: {
      "@schafevormfenster/enforce-log-before-throw": "error",
      "@schafevormfenster/no-sensitive-log-keys": "error"
    }
  },
  // Example: Apply E2E testing rules to e2e directory
  {
    files: ["**/e2e/**/*.spec.ts"],
    rules: {
      ...svfPlugin.configs["testing-e2e"].rules
    }
  },
  // Example: Apply Unit testing rules to unit test files
  {
    files: ["**/*.test.ts"],
    ignores: ["**/*.component.test.ts", "**/*.integration.test.ts"],
    rules: {
      ...svfPlugin.configs["testing-unit"].rules
    }
  },
  // Example: Apply Component testing rules to component test files
  {
    files: ["**/*.component.test.ts"],
    rules: {
      ...svfPlugin.configs["testing-component"].rules
    }
  },
  // Example: Apply Integration testing rules to integration test files
  {
    files: ["**/*.integration.test.ts"],
    rules: {
      ...svfPlugin.configs["testing-integration"].rules
    }
  },
  // Example: Apply test coverage rules to source files
  {
    files: ["**/services/**/*.ts", "**/coordinators/**/*.ts", "**/clients/**/*.ts"],
    ignores: ["**/*.test.*", "**/*.spec.*"],
    rules: {
      ...svfPlugin.configs["testing-coverage"].rules
    }
  }
];
```

## Rules

<!-- begin auto-generated rules list -->

💼 Configurations enabled in.\
⚠️ Configurations set to warn in.\
🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).

| Name                                                                             | Description                                                                                                         | 💼                                                                                               | ⚠️                                                                                               | 🔧 |
| :------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------ | :----------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------- | :- |
| [enforce-api-route-structure](docs/rules/enforce-api-route-structure.md)         | Enforce presence of required files for API routes (route.test.ts, *.contract.ts, *.schema.ts)                       | ![badge-rest][]                                                                                  |                                                                                                  |    |
| [enforce-apitest-env-vars](docs/rules/enforce-apitest-env-vars.md)               | Forbid process.env access in e2e/ unless variable starts with APITEST_ or is TEST_ENV                               | ![badge-testing][] ![badge-testing-e2e][]                                                        |                                                                                                  |    |
| [enforce-file-naming](docs/rules/enforce-file-naming.md)                         | Enforce kebab-case file naming with semantic suffixes                                                               |                                                                                                  | ![badge-architecture][]                                                                          |    |
| [enforce-folder-structure](docs/rules/enforce-folder-structure.md)               | Enforce src/ folders: app, clients, lib, types, test, services, coordinators                                        |                                                                                                  | ![badge-architecture][]                                                                          |    |
| [enforce-get-logger-category](docs/rules/enforce-get-logger-category.md)         | Enforce getLogger to be called with a category string                                                               | ![badge-logging][] ![badge-logging-legacy][]                                                     |                                                                                                  |    |
| [enforce-layer-dependencies](docs/rules/enforce-layer-dependencies.md)           | Enforce architectural layer dependency rules between clients, services, and coordinators                            | ![badge-architecture][]                                                                          |                                                                                                  |    |
| [enforce-log-before-throw](docs/rules/enforce-log-before-throw.md)               | Enforce logging before throwing an error (skipped in test files)                                                    | ![badge-logging][] ![badge-logging-legacy][]                                                     |                                                                                                  |    |
| [enforce-log-level-before-throw](docs/rules/enforce-log-level-before-throw.md)   | Enforce log level before throwing an error (skipped in test files)                                                  |                                                                                                  | ![badge-logging][] ![badge-logging-legacy][]                                                     |    |
| [enforce-schema-type-export](docs/rules/enforce-schema-type-export.md)           | Every exported Zod schema must have a corresponding exported TypeScript type using z.infer                          | ![badge-rest][]                                                                                  |                                                                                                  |    |
| [enforce-semantic-cache-headers](docs/rules/enforce-semantic-cache-headers.md)   | Ensure Cache-Control header is set in GET route handlers                                                            |                                                                                                  | ![badge-caching][]                                                                               |    |
| [enforce-structured-logging](docs/rules/enforce-structured-logging.md)           | Enforce structured logging (context object + message)                                                               | ![badge-logging][] ![badge-logging-legacy][]                                                     |                                                                                                  |    |
| [enforce-test-file-naming](docs/rules/enforce-test-file-naming.md)               | Enforce *.spec.ts for Playwright and *.test.ts / *.component.test.ts / *.integration.test.ts for Vitest            | ![badge-testing][] ![badge-testing-e2e][] ![badge-testing-component][] ![badge-testing-integration][] ![badge-testing-unit][] |                                                                                                  |    |
| [enforce-token-in-path](docs/rules/enforce-token-in-path.md)                     | Enforce that API routes either include [token] in path or validate params.token in handler                          | ![badge-rest][]                                                                                  |                                                                                                  |    |
| [no-env-preservation-in-tests](docs/rules/no-env-preservation-in-tests.md)       | Forbid preserving and restoring process.env values in tests - tests should set their own env vars                   | ![badge-testing][] ![badge-testing-e2e][] ![badge-testing-component][] ![badge-testing-integration][] ![badge-testing-unit][] |                                                                                                  | 🔧 |
| [no-env-vars-in-component-tests](docs/rules/no-env-vars-in-component-tests.md)   | Forbid process.env access in component tests — component tests must not depend on environment variables             | ![badge-testing][] ![badge-testing-component][]                                                  |                                                                                                  |    |
| [no-forbidden-imports](docs/rules/no-forbidden-imports.md)                       | Forbid libraries that have preferred alternatives per tech stack                                                    |                                                                                                  | ![badge-stack][]                                                                                 |    |
| [no-mocks-in-integration-tests](docs/rules/no-mocks-in-integration-tests.md)     | Forbid vi.mock(), vi.spyOn(), and vi.fn() in integration tests — must use real API calls                            | ![badge-testing][] ![badge-testing-integration][]                                                |                                                                                                  |    |
| [no-raw-logging](docs/rules/no-raw-logging.md)                                   | Prevent logging raw objects without a message                                                                       |                                                                                                  | ![badge-logging][] ![badge-logging-legacy][]                                                     |    |
| [no-redundant-normalize-error](docs/rules/no-redundant-normalize-error.md)       | Detect and forbid calls to normalizeError() inside log.error(), as the logger handles this internally               | ![badge-logging][] ![badge-logging-legacy][]                                                     |                                                                                                  |    |
| [no-reexport-files](docs/rules/no-reexport-files.md)                             | Prevent files that primarily re-export from other modules                                                           |                                                                                                  | ![badge-architecture][]                                                                          |    |
| [no-sensitive-log-keys](docs/rules/no-sensitive-log-keys.md)                     | Prevent logging of sensitive keys                                                                                   | ![badge-logging][] ![badge-logging-legacy][]                                                     |                                                                                                  |    |
| [no-skip-in-integration-tests](docs/rules/no-skip-in-integration-tests.md)       | Forbid .skip, .skipIf, and .todo in integration tests — integration tests must always run to validate external contracts | ![badge-testing][] ![badge-testing-integration][]                                                |                                                                                                  |    |
| [no-unsafe-log-property-access](docs/rules/no-unsafe-log-property-access.md)     | Prevent unsafe property access in log context                                                                       |                                                                                                  | ![badge-logging][] ![badge-logging-legacy][]                                                     |    |
| [no-use-cache-in-post](docs/rules/no-use-cache-in-post.md)                       | Forbid "use cache" directive in POST request handlers                                                               | ![badge-caching][]                                                                               |                                                                                                  |    |
| [one-function-per-file](docs/rules/one-function-per-file.md)                     | Warn if a file defines multiple top-level functions (exported or internal)                                         |                                                                                                  | ![badge-architecture][]                                                                          |    |
| [one-function-per-file-strict](docs/rules/one-function-per-file-strict.md)       | Error if a file defines more than 4 top-level functions (high priority when file is larger than 250 lines)        | ![badge-architecture][]                                                                          |                                                                                                  |    |
| [prefer-custom-logger](docs/rules/prefer-custom-logger.md)                       | Enforce use of custom logger instead of console                                                                     |                                                                                                  | ![badge-logging][] ![badge-logging-legacy][]                                                     |    |
| [prefer-loose-error-matching](docs/rules/prefer-loose-error-matching.md)         | Prefer regex matching over exact string matching for error messages                                                 |                                                                                                  | ![badge-testing][] ![badge-testing-component][] ![badge-testing-integration][] ![badge-testing-unit][]                        |    |
| [prefer-schema-extension](docs/rules/prefer-schema-extension.md)                 | Warn if a Zod object is defined from scratch without using .extend() or .merge() when it could reuse common schemas |                                                                                                  | ![badge-rest][]                                                                                  |    |
| [require-component-test](docs/rules/require-component-test.md)                   | Require *.component.test.ts files for services and coordinators                                                     |                                                                                                  | ![badge-testing][] ![badge-testing-coverage][]                                                   |    |
| [require-describe-wrapper](docs/rules/require-describe-wrapper.md)               | Require test cases to be wrapped in describe() blocks for better organization                                       |                                                                                                  | ![badge-testing][] ![badge-testing-e2e][] ![badge-testing-component][] ![badge-testing-integration][] ![badge-testing-unit][] |    |
| [require-env-setup-in-tests](docs/rules/require-env-setup-in-tests.md)           | Require beforeEach to initialize env vars when tests access process.env                                             |                                                                                                  | ![badge-testing][] ![badge-testing-integration][] ![badge-testing-unit][]                        | 🔧 |
| [require-error-in-log-error](docs/rules/require-error-in-log-error.md)           | Ensure log.error includes an error object                                                                           |                                                                                                  | ![badge-logging][] ![badge-logging-legacy][]                                                     |    |
| [require-integration-test](docs/rules/require-integration-test.md)               | Require *.integration.test.ts files for client files that call external APIs                                        |                                                                                                  | ![badge-testing][] ![badge-testing-coverage][]                                                   |    |
| [require-meaningful-descriptions](docs/rules/require-meaningful-descriptions.md) | Require meaningful descriptions for describe() and it()/test() blocks                                               |                                                                                                  | ![badge-testing][] ![badge-testing-e2e][] ![badge-testing-component][] ![badge-testing-integration][] ![badge-testing-unit][] |    |
| [require-mock-data-source-comment](docs/rules/require-mock-data-source-comment.md) | Require a provenance comment documenting the data source before mockResolvedValue, mockReturnValue, and related mock calls in unit tests |                                                                                                  | ![badge-testing][] ![badge-testing-unit][]                                                       |    |
| [suggest-test-categorization](docs/rules/suggest-test-categorization.md)         | Suggest organizing tests into categories for better structure                                                       |                                                                                                  | ![badge-testing][] ![badge-testing-e2e][] ![badge-testing-component][] ![badge-testing-integration][] ![badge-testing-unit][] |    |

<!-- end auto-generated rules list -->

[badge-architecture]: https://img.shields.io/badge/architecture-blue
[badge-caching]: https://img.shields.io/badge/caching-blue
[badge-logging]: https://img.shields.io/badge/logging-blue
[badge-logging-legacy]: https://img.shields.io/badge/logging--legacy-blue
[badge-rest]: https://img.shields.io/badge/rest-blue
[badge-stack]: https://img.shields.io/badge/stack-blue
[badge-testing]: https://img.shields.io/badge/testing-blue
[badge-testing-e2e]: https://img.shields.io/badge/testing--e2e-blue
[badge-testing-component]: https://img.shields.io/badge/testing--component-blue
[badge-testing-coverage]: https://img.shields.io/badge/testing--coverage-blue
[badge-testing-integration]: https://img.shields.io/badge/testing--integration-blue
[badge-testing-unit]: https://img.shields.io/badge/testing--unit-blue