tx-agent-kit
Enforcement

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.jsstrictTypeChecked preset and project-wide overrides
  • domain-invariants.js — Architecture, DDD layer boundaries, and import restrictions
  • code-quality.js — General code quality (unused imports, curly braces, modern JS style)
  • effect-consistency.js — Effect-specific patterns (ban new Promise)
  • type-safety.js — Exhaustive switches, nullish coalescing, optional chain, type exports
  • testing.js — Test-specific rules (vitest globals, no .only())
  • unicorn.js — Modern JS best practices from eslint-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 issue

These 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:

RuleEnforces
curlyBraces required on all blocks
object-shorthand{ foo } over { foo: foo }
prefer-arrow-callbackArrow callbacks (named functions allowed)
prefer-rest-params...args over arguments
prefer-spreadSpread over .apply()

Unicorn Rules

Modern JS best practices from eslint-plugin-unicorn:

RuleEnforces
unicorn/prefer-node-protocolnode: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-clonestructuredClone over JSON round-trip
unicorn/prefer-number-propertiesNumber.isNaN over global isNaN
unicorn/catch-error-nameConsistent error naming in catch blocks
unicorn/numeric-separators-style1_000_000 over 1000000
unicorn/no-useless-spreadNo redundant spreads
unicorn/no-useless-promise-resolve-rejectNo return Promise.resolve(x) in async functions
unicorn/no-abusive-eslint-disableNo blanket /* eslint-disable */
unicorn/prefer-optional-catch-bindingDrop 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 .

On this page