Code style, TypeScript, and naming
When to use: Biome formatting, import style, strict TypeScript, naming (including React file names), or generated files. For where domain code lives, see architecture-boundaries.
Code style (Biome)
Biome config (biome.json) is the source of truth:
- Indentation: 2 spaces
- Max line width: 120
- Strings: double quotes
- Semicolons: as needed
- Ignore generated/output paths:
dist/**, coverage/**, .turbo/**, node_modules/**, **/*.gen.ts, **/models.dev.json
- Prefer package-local formatting:
pnpm --filter @app/api format
- Cognitive complexity (
complexity/noExcessiveCognitiveComplexity) is enabled at max 15. Keep new and edited functions at or under that score — split nested conditionals, loops, and branches into named helpers rather than silencing the rule. Existing hotspots are warn-only until they are refactored; do not add complexity to them.
Imports
- Prefer static imports; avoid dynamic import patterns unless justified
- Use
import type { ... } for type-only imports
- Keep imports explicit and grep-friendly
- Preserve clear grouping/order (external, internal alias, then relative)
- Avoid wildcard exports/imports when explicit named exports are practical
- Avoid barrel files (
index.ts re-exporting from the same directory); import from the specific module
- Use
.ts/.tsx extensions in relative imports (not .js). The codebase uses TypeScript source extensions for module resolution
TypeScript
Base config: tsconfig.base.json. Typechecking runs under tsgo (TypeScript 7 beta, via @typescript/native-preview) — use tsgo -p tsconfig.json --noEmit in typecheck scripts, never tsc.
strict: true is enabled; keep code strict-clean
- Module system:
NodeNext + ESM ("type": "module" in packages/apps)
- For new domain data contracts, define the canonical shared shape as a Zod schema first when runtime validation is required, then infer TypeScript types from that schema or from Drizzle schemas where appropriate.
- Treat schemas in
src/entities/<entity>.ts as the canonical domain contract. Schemas and types elsewhere in the same domain or at app/platform boundaries should derive from or reuse those entity shapes whenever practical instead of restating identical fields.
- Canonical entity schemas should treat system-managed fields such as
id, createdAt, and updatedAt as core entity fields. Do not split an entity into a business payload plus an appended "persistence" wrapper unless there is a truly distinct boundary/input DTO that needs that separation.
- Enum-like contracts should use literal-string unions or
as const objects, not TypeScript enums.
- Use shared domain schemas to validate data crossing from app/platform boundaries into domain use-cases.
- If a boundary schema must differ materially from the entity shape, reuse domain constants, field schemas, and literal unions before introducing duplicated inline limits or sentinel values.
- Configurable thresholds, weights, debounce windows, sentinel values, and similar tunables should live in named constants inside the owning domain package rather than as scattered inline literals.
- Types and schemas that exist only as the inputs of one use-case should stay in that use-case file unless several use-cases truly share the same contract.
- Prefer explicit domain types/interfaces over loose objects
- Methods/functions with more than one argument should default to a single named-arguments object rather than positional arguments
- Use
readonly fields for immutable domain data shapes
- Avoid
any; use unknown + narrowing
- Avoid unnecessary type assertions (
as { ... }); prefer relying on inferred types from libraries
- Validate boundary inputs early (API input, queue payloads, external IO)
General principles
- Prefer minimal, explicit abstractions — YAGNI
- Comments: default to none, one line when needed — see the repo-wide convention in the root
AGENTS.md. Only traps (code that looks removable or wrong but isn't), non-obvious external constraints, and sibling-complete contract docs (e.g. port JSDoc) earn a comment; never what-narration or design rationale
Domain module layout
- Canonical entity schemas and inferred entity types belong in
src/entities/<entity>.ts.
- Domain package constants belong in
src/constants.ts.
- Domain package errors belong in
src/errors.ts.
- Small domain-scoped shared helpers such as predicates or lifecycle helpers belong in
src/helpers.ts.
Naming conventions
- Types/interfaces/classes:
PascalCase
- Variables/functions/methods:
camelCase
- Constants:
UPPER_SNAKE_CASE only for true constants; otherwise camelCase + as const
- Shared base schemas/types that are extended into discriminated unions or related variants should use
baseXxxSchema / BaseXxx naming rather than xxxCommonSchema / XxxCommon.
- File names favor concise module roots (
src/index.ts, src/server.ts, src/main.tsx)
- React component files use kebab-case:
my-component.tsx or my-component/index.tsx — never PascalCase file names (e.g. MyComponent.tsx). This matches the @repo/ui convention (table-skeleton.tsx, form-field.tsx, etc.)
- Package names follow scoped workspace style (
@app/*, @domain/*, etc.)
Generated files
apps/web/src/routeTree.gen.ts is auto-generated by TanStack Router — do not manually edit
- Generated files may be regenerated during builds or dev server runs; commit them when they change but do not modify by hand
1---2name: code-style3description: Biome formatting, import style, strict TypeScript, naming (including React file names), or generated files.4---56# Code style, TypeScript, and naming78**When to use:** Biome formatting, import style, strict TypeScript, naming (including React file names), or generated files. For **where** domain code lives, see [architecture-boundaries](../architecture-boundaries/SKILL.md).910## Code style (Biome)1112Biome config (`biome.json`) is the source of truth:1314- Indentation: 2 spaces15- Max line width: 12016- Strings: double quotes17- Semicolons: as needed18- Ignore generated/output paths: `dist/**`, `coverage/**`, `.turbo/**`, `node_modules/**`, `**/*.gen.ts`, `**/models.dev.json`19- Prefer package-local formatting: `pnpm --filter @app/api format`20- Cognitive complexity (`complexity/noExcessiveCognitiveComplexity`) is enabled at max **15**. Keep new and edited functions at or under that score — split nested conditionals, loops, and branches into named helpers rather than silencing the rule. Existing hotspots are warn-only until they are refactored; do not add complexity to them.2122## Imports2324- Prefer static imports; avoid dynamic import patterns unless justified25- Use `import type { ... }` for type-only imports26- Keep imports explicit and grep-friendly27- Preserve clear grouping/order (external, internal alias, then relative)28- Avoid wildcard exports/imports when explicit named exports are practical29- Avoid barrel files (`index.ts` re-exporting from the same directory); import from the specific module30- **Use `.ts`/`.tsx` extensions** in relative imports (not `.js`). The codebase uses TypeScript source extensions for module resolution3132## TypeScript3334Base config: `tsconfig.base.json`. Typechecking runs under **`tsgo`** (TypeScript 7 beta, via `@typescript/native-preview`) — use `tsgo -p tsconfig.json --noEmit` in `typecheck` scripts, never `tsc`.3536- `strict: true` is enabled; keep code strict-clean37- Module system: `NodeNext` + ESM (`"type": "module"` in packages/apps)38- For new domain data contracts, define the canonical shared shape as a Zod schema first when runtime validation is required, then infer TypeScript types from that schema or from Drizzle schemas where appropriate.39- Treat schemas in `src/entities/<entity>.ts` as the canonical domain contract. Schemas and types elsewhere in the same domain or at app/platform boundaries should derive from or reuse those entity shapes whenever practical instead of restating identical fields.40- Canonical entity schemas should treat system-managed fields such as `id`, `createdAt`, and `updatedAt` as core entity fields. Do not split an entity into a business payload plus an appended "persistence" wrapper unless there is a truly distinct boundary/input DTO that needs that separation.41- Enum-like contracts should use literal-string unions or `as const` objects, not TypeScript enums.42- Use shared domain schemas to validate data crossing from app/platform boundaries into domain use-cases.43- If a boundary schema must differ materially from the entity shape, reuse domain constants, field schemas, and literal unions before introducing duplicated inline limits or sentinel values.44- Configurable thresholds, weights, debounce windows, sentinel values, and similar tunables should live in named constants inside the owning domain package rather than as scattered inline literals.45- Types and schemas that exist only as the inputs of one use-case should stay in that use-case file unless several use-cases truly share the same contract.46- Prefer explicit domain types/interfaces over loose objects47- Methods/functions with more than one argument should default to a single named-arguments object rather than positional arguments48- Use `readonly` fields for immutable domain data shapes49- Avoid `any`; use `unknown` + narrowing50- Avoid unnecessary type assertions (`as { ... }`); prefer relying on inferred types from libraries51- Validate boundary inputs early (API input, queue payloads, external IO)5253## General principles5455- Prefer minimal, explicit abstractions — YAGNI56- Comments: default to none, one line when needed — see the repo-wide convention in the root `AGENTS.md`. Only traps (code that looks removable or wrong but isn't), non-obvious external constraints, and sibling-complete contract docs (e.g. port JSDoc) earn a comment; never what-narration or design rationale5758## Domain module layout5960- Canonical entity schemas and inferred entity types belong in `src/entities/<entity>.ts`.61- Domain package constants belong in `src/constants.ts`.62- Domain package errors belong in `src/errors.ts`.63- Small domain-scoped shared helpers such as predicates or lifecycle helpers belong in `src/helpers.ts`.6465## Naming conventions6667- Types/interfaces/classes: `PascalCase`68- Variables/functions/methods: `camelCase`69- Constants: `UPPER_SNAKE_CASE` only for true constants; otherwise `camelCase` + `as const`70- Shared base schemas/types that are extended into discriminated unions or related variants should use `baseXxxSchema` / `BaseXxx` naming rather than `xxxCommonSchema` / `XxxCommon`.71- File names favor concise module roots (`src/index.ts`, `src/server.ts`, `src/main.tsx`)72- React component files use **kebab-case**: `my-component.tsx` or `my-component/index.tsx` — never `PascalCase` file names (e.g. `MyComponent.tsx`). This matches the `@repo/ui` convention (`table-skeleton.tsx`, `form-field.tsx`, etc.)73- Package names follow scoped workspace style (`@app/*`, `@domain/*`, etc.)7475## Generated files7677- `apps/web/src/routeTree.gen.ts` is auto-generated by TanStack Router — do not manually edit78- Generated files may be regenerated during builds or dev server runs; commit them when they change but do not modify by hand