Lintmax
Lintmax lint/format orchestrator conventions
lintmax = biome + oxlint + eslint + prettier + sort-package-json in one command; we own it.
Every lintmax version runs configless by default. lintmax (TypeScript) is the sole exception — its max-strict default is dense enough that a project needs lintmax.config.ts to opt a rule out, so it supports one; every other version (lintmax-go, lintmax-rs, …) stays config-free. A project config opts a rule out only with documented false-positive evidence, never to dodge a fix.
MUST
- Run only
bun run fixfor code maintenance. Why: it fixes then verifies internally (all 5 linters twice); a clean run printsokon a single line + exit 0 —okIS the success signal, not silence. - Read failure output directly. Why: already grouped file→linter→rule, compressed line numbers, deduped across 5 linters.
- Make ALL edits first, then run
fixforeground to completion. Why: editing during a backgroundedfixraces it — the formatter writes its pre-edit buffer back and silently reverts your change. - WHEN a
fixis running, wait untilpgrep -f 'lintmax|bun.*fix'is clear before editing. Why: same revert race. - Commit a checkpoint before any multi-file mutator (
fixafter stripping directives, audit/codemod, sed-all/rename-all). Why:fixmixes autofixes with your edits;git reset --hardthen restores in one command. - Batch many edits, run
fixonce at the end. Why:fixis slow per run. - File code-lint gaps upstream against lintmax. Why: it is the only lint tool — domain-specific hand-rolled
tools/*.tschecks (banned vocab, spec-vs-code diff) are fine; code-lint is not.
NEVER
- Run
bun run check/lintmax checkfor maintenance —checkis CI-only;fixis the agent-side maintenance command. Cost: redundant afterfix, wastes 2+ min re-running 5 linters. | tail/| headon any lintmax command. Cost: empty output IS success; failure output is already agent-formatted — truncation hides violations.lintmax check --humanto “see violations”. Cost: runbun run fixand read its failure output.- Add a second code-lint tool — extra eslint plugins, stylelint, knip, depcheck, dependency-cruiser, size-limit. Cost: fragments lintmax’s curated surface, drifts.
- Use the
voidoperator. Cost:fixauto-deletes it (no-void) —void promise()→ bare expr →noUnusedExpressions;() => { void mutate() }→() => { undefined }, dropping the call.
void replacements
- Unused promise:
promise.catch(() => {})ortry { await ... } catch {}. - Async in a
() => voidslot (onClick):() => { mutate().catch(console.error) }, or widen the prop type to() => void | Promise<void>. - Async inside a
useEffectbody (slot type can’t be widened): wrap in an IIFE;(async () => { ... })()or.catch(noop). - Unused var: rename
_xor remove it.
Ignore syntax
| Linter | File-level | Per-line |
|---|---|---|
| oxlint | /* oxlint-disable rule */ | // oxlint-disable-next-line rule |
| eslint | /* eslint-disable rule */ | // eslint-disable-next-line rule |
| biome | /** biome-ignore-all lint/cat/rule: reason */ | /** biome-ignore lint/cat/rule: reason */ |
Ignore strategy
- Fix every legit, fixable finding by fixing the code, never the rule; ignore is last resort. Why: a found-and-fixable finding disabled is the severity/effort loophole that lets a real defect rot — the rule stays, the code changes. The only legitimate disable is a documented false-positive-rate, logged as an exception.
- File-level disable WHEN a file has many unavoidable same-rule violations (sequential DB mutations, standard React patterns, external images); per-line for an isolated one. Why: scale-appropriate.
- File-level directive at absolute file top, above imports/code (incl
'use client'/'use node'); per-line on the line ABOVE the code. Why: per-line inline tripsno-inline-comments. - WHEN 2+ linters flag one line, file-level for one + per-line for the other. Why: stacking multiple per-line above one line is banned.
- One top
eslint-disableper file, multiple rules comma-joined; keep one canonical block, remove duplicates. Why: dedupe. - WHEN a file-level
biome-ignore-allexists, drop the redundant per-linebiome-ignorefor that same rule. Why: file-level already covers every line. - NEVER 5+ per-line ignores for one rule. Cost: use file-level instead.
- Don’t hand-remove dead directives or add one “just in case”. Why:
fixauto-removes UNUSED file-leveloxlint-disable/biome-ignore-all(both/**and//forms) by strip-relint-in-place; if a rule doesn’t fire,fixdrops it andcheckfails on it.
Cross-linter
- Same rule in 2 linters (biome
noAwaitInLoops+ oxlintno-await-in-loop) = double enforcement, not conflict — never disable one. Why: both must pass. - Suppress a shared eslint/oxlint rule on eslint’s side. Why: oxlint auto-picks up eslint rules and is faster.
- oxlint
eslint/sort-keysis disabled in lintmax. Why: conflicts with perfectionist (ASCII vs natural sort).
Never-ignore rules
lintmax check FAILS on these suppressions, used or unused — no suppress-for-now path reaches CI. Fix the code:
@typescript-eslint/no-unsafe-*(assignment, call, member-access, return, argument) — use proper types.@typescript-eslint/no-explicit-any— define the actual type.@ts-ignore/@ts-expect-error/@ts-nocheck— fix the type error.@typescript-eslint/no-non-null-assertion— handle the null case.
Fixes, not suppressions:
- Test-file exception:
@ts-expect-error+no-explicit-anyallowed in test files only (asserting a wrong type is rejected); the rest forbidden everywhere. - Untyped third-party dep (types resolve to
any: brokenexports.types, unresolvedtypeof import(...)): cast through a typed facade at one boundary —const get = rawGet as <T>(k: string) => Promise<T | undefined>, orconst x: unknown = await loader.init(); return x as MonacoApiwith a minimal interface. Neveras any. - Non-null (
x[i]!): null-check (const v = x[i]; if (v) ...) orconst-tuple ([...] as const) so fixed indices type as defined. no-unsafe-*on a visible-shape stub:(() => undefined) as never(bottom type, no visible ops);((..._: unknown[]) => ({})) as neverstill trips. Tighten withnever/ branded / generic.
Safe-to-ignore
- oxlint:
promise/prefer-await-to-then(Promise.race, ky chaining). - eslint:
no-await-in-loop,max-statements,max-depth,complexity(sequential ops) ·no-unnecessary-condition(narrowing) ·promise-function-async(thenable returns) ·max-params·@next/next/no-img-element(external images) ·react-hooks/refs. - biome:
style/noProcessEnv(env files) ·performance/noAwaitInLoops(sequential ops) ·nursery/noForIn·performance/noImgElement·suspicious/noExplicitAny(generic boundaries).
Playbook maintenance
- Merge each new lesson into the most relevant existing section immediately; correct rules in place, remove superseded guidance. Why: single source of truth, no append-only “recent lessons” buckets.