Complexity and Naming
ESLint complexity limits, hand-picked sonarjs rules, and naming convention enforcement -- the editor-time layer of architecture enforcement.
The editor-time enforcement layer uses ESLint to catch complexity violations and naming inconsistencies while code is being written. This gives AI agents (and the founder) instant red squiggles -- the fastest possible feedback loop.
For the complete ESLint configuration reference (preset architecture, all config options, SDK boundary enforcement), see Quality Gates — ESLint Configuration. This page focuses on the architecture enforcement rationale behind complexity limits and naming conventions.
Complexity Limits
Six metrics are enforced globally, with relaxed thresholds for specific file types.
| Metric | Rule | Threshold | What It Measures |
|---|---|---|---|
| File size | max-lines | 400 (skip blank/comments) | Total lines of logic per file |
| Function size | max-lines-per-function | 100 (skip blank/comments) | Total lines of logic per function |
| Cyclomatic complexity | complexity | 20 | Number of independent paths through a function |
| Cognitive complexity | sonarjs/cognitive-complexity | 20 | How hard a function is to understand (penalizes nesting, flow breaks, recursion) |
| Nesting depth | max-depth | 4 | Maximum nesting level of blocks |
| Parameter count | max-params | 4 | Forces use of option objects for complex signatures |
Cyclomatic vs Cognitive Complexity
Cyclomatic complexity counts branch points (decision paths through code). Cognitive complexity measures how hard code is to understand for a human reader. The difference matters:
// Cyclomatic: 5, Cognitive: 5 (simple branching)
function route(method: string) {
if (method === "GET") return handleGet();
if (method === "POST") return handlePost();
if (method === "PUT") return handlePut();
if (method === "DELETE") return handleDelete();
return handleNotAllowed();
}
// Cyclomatic: 4, Cognitive: 10 (nesting makes it harder to read)
function process(data: unknown) {
if (data) {
// +1 cyclo, +1 cogn
if (Array.isArray(data)) {
// +1 cyclo, +2 cogn (nesting)
for (const item of data) {
// +1 cyclo, +3 cogn (nesting)
if (typeof item === "string") {
// +1 cyclo, +4 cogn (nesting)
// ...
}
}
}
}
}
Both are capped at 20 in this project. The founder chose 20 over the commonly recommended 15 because the codebase includes data-heavy handlers that naturally branch more than typical application code.
Relaxed File Types
Complexity limits are turned off for files that are inherently large or repetitive:
**/seeds/**-- seed data files**/migrations/**-- generated migration SQL**/schema/**-- Drizzle schema definitions (table declarations, not logic)**/__tests__/**,**/*.test.*,**/*.spec.*-- test suites
JSDoc requirements are also disabled for these file types.
Hand-Picked sonarjs Rules
9 rules were selected from eslint-plugin-sonarjs, explicitly rejecting the full recommended preset (150+ error-level rules that produced unacceptable false positives). These 9 target the specific code smells AI agents produce most frequently.
| Rule | What It Catches |
|---|---|
sonarjs/cognitive-complexity | Functions too hard to understand (threshold: 20) |
sonarjs/no-identical-functions | Two functions with identical bodies (copy-paste artifact) |
sonarjs/no-collapsible-if | Nested if that can be combined with && |
sonarjs/no-duplicated-branches | if/else with identical code in branches |
sonarjs/no-all-duplicated-branches | All branches of a conditional are identical |
sonarjs/no-identical-expressions | a === a or x && x (always a bug) |
sonarjs/prefer-single-boolean-return | if (x) return true; return false; |
sonarjs/no-redundant-jump | continue, break, return with no effect |
sonarjs/no-nested-conditional | Ternaries inside ternaries |
Naming Conventions
@typescript-eslint/naming-convention enforces consistent casing across the monorepo. The rules are tuned for the project's specific needs (React components, Next.js route handlers, external API boundary types).
| Selector | Allowed Formats | Notes |
|---|---|---|
| Default | camelCase | Baseline for all identifiers |
| Variables | camelCase, PascalCase, UPPER_CASE | PascalCase for React components, UPPER_CASE for constants |
| Destructured variables | Exempt | Match external APIs, props, env vars |
__ prefix variables | Exempt | Node.js shims like __dirname |
| Functions | camelCase, PascalCase | PascalCase for React components |
| Exported functions matching HTTP verbs | UPPER_CASE only | GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS |
| Parameters | camelCase, PascalCase | Leading underscore allowed for unused |
| Object literal properties | Exempt | Drizzle schemas, API payloads, config objects |
| Type properties | camelCase, snake_case | snake_case for external API boundary types (Anthropic, Typesense) |
| Types and classes | PascalCase | Includes interfaces, type aliases, enums |
| Type parameters | PascalCase with T prefix | TResult, TItem, TContext |
| Imports | camelCase, PascalCase | Covers both utility and component imports |
Other Editor-Time Rules
| Rule | Severity | What It Enforces |
|---|---|---|
no-console | error | Bans console.log -- use @repo/logger instead |
@typescript-eslint/only-throw-error | error | Only throw Error objects, not strings or plain objects |
unicorn/expiring-todo-comments | error | Every TODO/FIXME/HACK must have [YYYY-MM-DD] or [TRO-*] |
tsdoc/syntax | warn | Catches malformed TSDoc tags and non-standard syntax |
jsdoc/require-jsdoc | error | Exported functions/classes/types with 3+ lines must have JSDoc |
simple-import-sort/imports | error | Enforces import group ordering (side effects, React/Next, external, @repo/*, relative, CSS) |
@typescript-eslint/consistent-type-imports | error | Use type imports for type-only references |
@typescript-eslint/no-explicit-any | error | Bans any type annotations |
Import Sort Order
Imports are sorted into six groups by simple-import-sort:
- Side-effect imports (
import "...") - React, React DOM, Next.js
- External packages (
@?\\w) - Internal packages (
@repo/*) - Relative imports (
./) - CSS imports
Related Pages
- Delivery -- Quality Gates -- pre-commit hook that runs ESLint --fix (forward-reference, being built in parallel)
- Review and Audits -- the
/arch-reviewskill checks complexity scores as part of its assessment