Code style
A tool decides half of the style here, and review decides the rest. A reviewer who has to re-derive a rule from the diff every time eventually stops asking for it, so the second half is written down. Each section says which half it is.
Formatting
Section titled “Formatting”Prettier owns formatting, and .prettierrc at the root contains every option it was given. The format lane runs pnpm format:check, and pnpm format rewrites files in place.
proseWrap is set to never, so Prettier joins a hand-wrapped Markdown paragraph back onto one line. Write a full thought on one physical line however long it runs, and a diff then shows which sentence changed instead of where the column ran out.
Imports
Section titled “Imports”Imports come in a fixed order with a blank line between the groups and alphabetical order inside each: Node builtins, then external packages, then @supolka/*, then relative paths. @ianvs/prettier-plugin-sort-imports enforces it from the same .prettierrc, so a wrong import order fails the format lane, and pnpm format is the fix.
A statement that pulls in values alongside types marks the types inline with { type X }. An import of types alone uses import type. The lint lane enforces both, and the four groups take this shape:
import { availableParallelism } from 'node:os'import { fileURLToPath } from 'node:url'
import { RpcSession, type RpcStub } from 'capnweb'
import { ExecutorError, type ExecuteResultMessage } from '@supolka/codemode-executor-protocol'
import type { PoolEvents } from './events.js'import { Worker, type WorkerOptions } from './supervisor.js'type RpcStub sits inline because RpcSession beside it is a value, and PoolEvents takes a statement of its own because nothing else on that line is a value. verbatimModuleSyntax is the reason for the split, and tsconfig.base.json turns it on: under it, an all-type import written with inline specifiers still emits a live import {} and loads the module at runtime for no reason.
Modules a neighbour borrows only a type from must not appear in that neighbour’s runtime import graph. Extract the pure part into its own module instead. Nothing enforces this one except review.
Blank lines
Section titled “Blank lines”Put a blank line before every return and between logical blocks. Review is the only enforcement, because no lint rule here covers blank lines between statements. Blank lines between test blocks are the exception, and the lint lane covers those.
Weak, because the guard, the arithmetic and the answer run together with nothing between them:
const describeBound = (limit: number, used: number) => { const remaining = limit - used if (remaining <= 0) { return 'at the bound' } const share = Math.round((used / limit) * 100) return `${share}% of the bound`}Strong, because a reader sees the three steps without reading the whole body:
const describeBound = (limit: number, used: number) => { const remaining = limit - used
if (remaining <= 0) { return 'at the bound' }
const share = Math.round((used / limit) * 100)
return `${share}% of the bound`}remaining decides whether there is anything to report, and share is computed only once that guard has passed.
Linting
Section titled “Linting”Linting is oxlint through the root .oxlintrc.json, never ESLint. Two categories run at error, correctness and suspicious, so any hit fails the lint lane. Biome is the other current-generation choice, and its value is the formatter and the linter together: beside Prettier half of it would be switched off, and it would still own a second configuration file in full.
@nx/enforce-module-boundaries is an ESLint rule, so no lane can run it. pnpm’s isolated node_modules and TypeScript project references close that boundary instead: an undeclared cross-package import fails to resolve, at install and again at typecheck.
Type-aware rules are the gap left over. no-floating-promises runs in neither strict tsc nor oxlint here, so no lane reports a dropped promise. Never swallow a rejection in supervisor or pool code: an unhandled one surfaces through Node’s unhandledRejection, and that is the only report there is.
No machine can check how a lint conflict gets resolved, so the resolution is written down.
- Never make a suppression the answer. Move the code until the rule and the semantics agree, or change the rule tree-wide. Per-line disables freeze a disagreement into the one file nobody will ever revisit.
- Never weaken a healthy default to make a check pass. Fix the artifact that tripped it.
Fix a type collision by changing the structure, never by casting. The typecheck lane cannot see through a cast, so an assertion the code never proved passes every lane, and the next reader has nothing to check it against.
Prefer the platform idiom wherever the platform owns the concern. Hand-rolled equivalents have to be maintained and eventually debugged, and Module design states that rule with its standing example.
Rules a tool checks, and rules review carries
Section titled “Rules a tool checks, and rules review carries”| Rule | Tool | Lane |
|---|---|---|
| Formatting, prose included | Prettier | format |
| Import grouping and order | @ianvs/prettier-plugin-sort-imports |
format |
| Inline versus statement type imports | oxlint | lint |
| Correctness and suspicious categories | oxlint | lint |
| Blank lines between test blocks | oxlint jest rules | lint |
| Type safety | TypeScript | typecheck |
Every rule this page states outside that table is one review has to catch. Tooling lists every lane, including the lanes that have nothing to do with style.