ESLint Rules
ESLint enforcement layer covering import restrictions, code patterns, and package boundaries.
The ESLint configuration lives in packages/tooling/eslint-config/ and provides per-file enforcement of architectural constraints. Config files are split by concern:
base.js—strictTypeCheckedpreset and project-wide overridesdomain-invariants.js— Architecture, DDD layer boundaries, and import restrictionscode-quality.js— General code quality (unused imports, curly braces, modern JS style)effect-consistency.js— Effect-specific patterns (bannew Promise)type-safety.js— Exhaustive switches, nullish coalescing, optional chain, type exportstesting.js— Test-specific rules (vitest globals, no.only())unicorn.js— Modern JS best practices fromeslint-plugin-unicorn
Import Restrictions
No console.*
All logging must go through @tx-agent-kit/logging:
// Forbidden
console.log('Server started')
// Required
import { createLogger } from '@tx-agent-kit/logging'
const logger = createLogger('my-service')
logger.info('Server started')No drizzle-orm Outside packages/infra/db
Database access is fully isolated. Only packages/infra/db may import from drizzle-orm:
// Only allowed in packages/infra/db/src/**
import { pgTable, uuid, varchar } from 'drizzle-orm/pg-core'Any other package importing drizzle-orm will fail lint.
No effect in apps/web
The web app must remain a simple client. Effect runtime logic belongs in API, core, or worker layers:
// Forbidden in apps/web/**
import { Effect } from 'effect'
import { Schema } from 'effect/Schema'No next/server or next/headers in apps/web
The web app is client-only. Server-side Next.js imports are forbidden:
// Forbidden in apps/web/**
import { NextResponse } from 'next/server'
import { cookies } from 'next/headers'Code Pattern Rules
No as any Assertions
Type assertions to any bypass the type system. Model unknowns explicitly and decode at boundaries:
// Forbidden
const data = response as any
// Required: decode with schema
const data = Schema.decodeUnknown(MySchema)(response)No Chained Assertions
Double assertions (as unknown as T) are forbidden. Use proper schema decoding:
// Forbidden
const value = input as unknown as MyType
// Required: decode at boundary
const value = Schema.decodeUnknown(MySchema)(input)No Suppression Directives
@ts-ignore, @ts-expect-error, and eslint-disable are forbidden in source modules. Fix the root cause:
// Forbidden
// @ts-ignore
// @ts-expect-error
// eslint-disable-next-line
// Required: fix the actual type or rule issueThese are allowed in test files and generated code.
No Default Exports in Domain Layers
Domain, port, application, adapter, and route files must use named exports:
// Forbidden
export default class OrganizationService { }
// Required
export class OrganizationService { }Code Quality Rules
No .only() in Test Files
A committed .only() silently skips the rest of the test suite in CI:
// Forbidden in *.test.ts and *.integration.test.ts
describe.only('my test', () => {})
it.only('single test', () => {})No Constant Binary Expressions
Provably-wrong binary expressions are forbidden (no-constant-binary-expression):
// Forbidden — always truthy
const x = "hello" ?? "world"Dead Import Removal
Unused imports are auto-removed via eslint-plugin-unused-imports:
// Auto-fixed: removed if foo is unused
import { foo } from './bar'Modern JS Style
The following modern JavaScript patterns are enforced:
| Rule | Enforces |
|---|---|
curly | Braces required on all blocks |
object-shorthand | { foo } over { foo: foo } |
prefer-arrow-callback | Arrow callbacks (named functions allowed) |
prefer-rest-params | ...args over arguments |
prefer-spread | Spread over .apply() |
Unicorn Rules
Modern JS best practices from eslint-plugin-unicorn:
| Rule | Enforces |
|---|---|
unicorn/prefer-node-protocol | node:fs over fs |
unicorn/prefer-string-replace-all | .replaceAll() over .replace(/…/g) |
unicorn/prefer-string-slice | .slice() over .substring()/.substr() |
unicorn/prefer-array-flat-map | .flatMap() over .map().flat() |
unicorn/prefer-array-some | .some() over .filter().length |
unicorn/prefer-structured-clone | structuredClone over JSON round-trip |
unicorn/prefer-number-properties | Number.isNaN over global isNaN |
unicorn/catch-error-name | Consistent error naming in catch blocks |
unicorn/numeric-separators-style | 1_000_000 over 1000000 |
unicorn/no-useless-spread | No redundant spreads |
unicorn/no-useless-promise-resolve-reject | No return Promise.resolve(x) in async functions |
unicorn/no-abusive-eslint-disable | No blanket /* eslint-disable */ |
unicorn/prefer-optional-catch-binding | Drop unused catch (error) bindings |
Package Boundaries
The eslint-plugin-boundaries configuration enforces dependency direction between packages. For example, apps/web cannot import from packages/infra/db or packages/core, and packages/core cannot import from packages/infra/db. On the other hand, apps/api can import from both packages/core and packages/infra/db.
Running ESLint
# Full output
pnpm lint
# Or just ESLint without structural/shell checks
npx eslint .