Removed
- The Biome lane. The
@howells/lint/biome/core,/biome/reactand/biome/nextpresets, thehowells-biomebinary, and the@biomejs/biomedependency are gone. Oxlint/Oxfmt is the whole toolchain. Seven repositories held abiome.jsonextending these presets at the point of removal, but only two still ran Biome from alintscript — the other five had already moved tohowells-checkand left dead config behind. This ships as a major precisely so nothing is taken by surprise: a consumer only crosses 2.0.0 by asking for it, and 1.x keeps working for as long as a project stays there.docs/adr/0003-remove-the-biome-lane.mdrecords the reasoning;docs/adr/0002is marked superseded rather than deleted.
ESLint is unaffected and is not going anywhere. It is not a lane and never was: eslint-plugin-github, eslint-plugin-sonarjs and eslint-plugin-playwright run inside Oxlint through its JS-plugin bridge, and account for 188 of the core preset's 678 enabled rules plus all 36 in the Playwright preset. The pinned eslint dependency is the runtime they resolve against.
Added
- The core preset carries Ultracite's build of the anti-slop plugin, so React, Next and Playwright inherit it. It rejects the low-evidence TypeScript that turns up when code is written fast: type assertions with no stated reason,
unknownin parameters and returns,Reflect.get/Reflect.applyproperty access, module mocking, widen-then-assert. No new dependency — the plugin is unpublished upstream and Ultracite vendors a bundled build. It is extended last, after Ultracite's core preset, because it disablestypescript/consistent-indexed-object-styleandunicorn/no-immediate-mutation, which deadlock againstanti-slop/no-known-value-widening— the autofix of one produces the input of the other. Nothing after core turns them back on, so React and Next inherit the resolution too. - The core preset carries Ultracite's Vitest rules, scoped by upstream to
*.test.*,*.spec.*, and__tests__files. This replaces the twelve rules this package hand-rolled at top level with roughly sixty, and picks up the conflict resolutions Ultracite maintains against them (valid-titleoff againstprefer-describe-function-title,prefer-called-timesoff againstprefer-called-once, and so on). Vitest rules no longer apply outside test files, which is where they were always inert anyway.
Changed
- Refresh the toolchain: Ultracite 7.10.5, Oxlint 1.78.0, Oxfmt 0.63.0, React Doctor's Oxlint plugin 0.9.12,
oxc-parser0.144.0. The JS-plugin bridge set (GitHub 6.1.2, SonarJS 4.2.0, Playwright 2.11.0) andoxlint-tsgolint7.0.2001 are unchanged and already current.
Fixed
- The Next preset accepts a route file's segment exports again. React Doctor 0.9.x rewrote its port of the react-refresh rule with a stripped-down default — no framework detection, no route-file awareness — so under 0.9.12 every
page.tsxexportingmetadata,dynamic,revalidate, orgenerateStaticParamsalongside its component reported itself as unsafe for Fast Refresh. Upstream's remedy issettings: { "react-doctor": { portedRuleMode: "curated" } }, which a shared preset cannot deliver: Oxlint readssettingsfrom the root config only and does not merge it throughextends, so it would have to be pasted into every consumer's ownoxlint.config.ts. The React preset keeps React Doctor's default, which is the genuine react-refresh contract. The Next preset turns the ported rule off and runs Oxlint's nativereact/only-export-componentswith an allowlist of Next's route-segment export names — rule options, unlikesettings, do flow throughextends.
Notes
- Oxlint 1.78.0 no longer lints a path it considers ignored —
node_modules, any dot-prefixed directory, anything matched by.gitignore— even when that path is named explicitly on the command line, and--no-ignoredoes not lift it. It reports "No files found to lint" instead. This package's own preset fixtures lived undernode_modulesfor plugin resolution and now run from a temp directory with anode_modulessymlink. A consumer linting a hidden directory (.storybook,.github/scripts) will see the same silence;howells-checksurfaces it as "Expected at least one target file". - ESLint stays on 9.39.5 and TypeScript on 6.0.3. Both holds were rechecked against this refresh and both still stand for the reasons given in 1.2.0:
eslint-plugin-githubstill pullseslint-plugin-import@2.32.0andeslint-plugin-jsx-a11y@6.10.2, neither of which admits ESLint 10, and@typescript-eslint/utilsstill declares peertypescript >=4.8.4 <6.1.0.