---
changelog:
  category: Release
  version: ultracite@7.12.2
date: '2026-09-29T05:44:28Z'
seo:
  description: >-
    ultracite init no longer wipes an existing Biome config it can't read.
    Previously a biome.json or biome.jsonc with a syntax error, or a nested
    monorepo config…
title: ultracite@7.12.2
type: changelog
---
## Patch Changes

- 0f192a7: `ultracite init` no longer wipes an existing Biome config it can't read. Previously a `biome.json` or `biome.jsonc` with a syntax error, or a nested monorepo config with `"extends": "//"`, was treated as empty and replaced with just the Ultracite `extends`, losing every other setting. Now:
  
  - A config with a syntax error is left unchanged, with a warning asking you to fix it and re-run `init`.
  - A nested config that extends the root config (`"extends": "//"`) is left unchanged, since the Ultracite presets belong in the root config.
  - A string `extends` is turned into a list that also includes the Ultracite presets.
  - Updates edit the file in place, so comments and formatting in `biome.jsonc` are preserved.
  
  `ultracite doctor` and the `check`/`fix` resolution check now follow a nested config that extends `"//"` to the root config, instead of warning that the nested config doesn't extend `ultracite/biome/core`.
- c68ba48: `ultracite check` and `ultracite fix` handle their arguments and missing tools more reliably:
  
  - `check` now treats explicit files the way `fix` does. Oxlint only gets files it can lint, Prettier runs with `--ignore-unknown`, and oxfmt with `--no-error-on-unmatched-pattern`. Before, `ultracite check README.md` or `ultracite check Dockerfile src/index.ts` failed even though nothing was wrong.
  - Linter flags that take a value keep it, even when the value looks like a file: `--tsconfig tsconfig.json`, `-c .oxlintrc.json`, `--config-path biome.json`, `--only lint/suspicious/noDebugger`, `--since origin/main` and similar. Before, the value was treated as a lint target, so `ultracite fix --tsconfig tsconfig.json` skipped Oxlint entirely and only formatted `tsconfig.json`. Ultracite's own `--claude`, `--codex`, `--hook` and `--unsafe` never take a value, so the next argument is always a target.
  - `ultracite fix --unsafe` with the ESLint toolchain no longer fails with ESLint's "Invalid option '--unsafe'". ESLint has no unsafe fixes, so the flag is dropped with a warning.
  - A linter that isn't installed is reported with a plain message instead of a stack trace. The other tools still run first. Stylelint is optional in the ESLint toolchain, as `ultracite doctor` already said, so a project without it now skips CSS linting with a warning instead of failing. "No linter configuration found" is also printed without a stack trace.
  - Linters installed in the project's `node_modules/.bin` are found even when Ultracite isn't run through a package manager script, `npx` or `bunx`, for example `./node_modules/.bin/ultracite check`.
- f61f393: `ultracite init` now looks for existing ESLint, Prettier and Stylelint configs in the same order the tools do, so when a project has more than one, init updates the one the tool actually loads. For example, Prettier reads `.prettierrc.json` before `prettier.config.mjs`, and ESLint reads `eslint.config.js` before `eslint.config.mjs`. Before, init could update a config the tool ignored and leave the active one in place. Stylelint's `.stylelintrc.ts` and `stylelint.config.ts` are now recognised too.
- 242cd2a: `ultracite init` now updates an existing `.vscode/settings.json` or `.zed/settings.json` in place, so your comments and formatting are kept. Before, the file was re-serialised as plain JSON, which stripped every comment. A settings file with a syntax error is now left unchanged with a warning. Before, it was rewritten with whatever part the parser could recover, which dropped the rest.
  
  For the ESLint toolchain, init now also installs the Prettier VS Code extension (`esbenp.prettier-vscode`), since the settings it writes make Prettier the default formatter. Before, only the ESLint extension was installed, so format-on-save did nothing until you added Prettier yourself.
- c273393: `ultracite init` now checks every flag value before it changes anything in the project. An unknown value for `--linter`, `--pm`, `--frameworks`, `--editors`, `--agents`, `--hooks`, `--integrations` or `--js-plugins` stops init with a message listing the valid values. Previously a misspelled `--linter` (for example `--linter Biome`) deleted every existing Biome, ESLint, Prettier, Stylelint, Oxlint and oxfmt config file and then crashed.
  
  When `--linter` is not passed and init runs without prompts (because of `--quiet`, `CI`, or flags such as `--agents` or `--pm`), it now keeps the linter the project is already set up with and only falls back to Oxlint when there is none. Running `ultracite init --agents universal` on a Biome project no longer migrates it to Oxlint. The interactive linter prompt also preselects the detected linter.
- 3cb2e71: Clearer wording in the CLI:
  
  - `ultracite init --help` now lists the valid values for `--pm`, `--linter`, `--frameworks`, `--hooks` and `--integrations`, and explains what `--type-aware` does for Biome and for Oxlint.
  - The agent rules file says "Oxlint + Oxfmt will catch most mechanical issues automatically" instead of "Oxlint + Oxfmt's linter will catch…".
  - `init` and `upgrade` say "Using pnpm (detected from the project)" instead of "Detected lockfile", since the package manager can also come from `packageManager`.
  - `ultracite doctor` spells "unrecognized" consistently, formats commands as code, and describes warnings as "Some checks have warnings" instead of "optional improvements".
- 6ae8be8: The ESLint `nestjs` preset imports `@darraghor/eslint-plugin-nestjs-typed`, but `ultracite init --linter eslint --frameworks nestjs` never installed it, so ESLint failed to load the config with "Cannot find package". `init` now installs the plugin with the preset, and `ultracite upgrade` installs the plugins of every framework preset your `eslint.config.*` imports, so existing NestJS projects pick it up on their next upgrade.
- a8e83bb: `ultracite init` now migrates an existing `.oxlintrc.json`, `.oxfmtrc.json` or `.oxfmtrc.jsonc` when it sets up Oxlint. Oxlint and oxfmt refuse to load any config when a JSON config sits next to `oxlint.config.ts` or `oxfmt.config.ts`, and init used to write the TS configs beside the JSON ones, so `ultracite check` and `ultracite fix` stopped working. Init now moves the JSON config's settings into the TS config and deletes the JSON file:
  
  - `rules`, `overrides`, `env`, `plugins` and other options become properties of the generated config.
  - `ignorePatterns`, `settings` and `jsPlugins` are added to the ones Ultracite generates instead of replacing them.
  - Ultracite `extends` entries become presets. Other `extends` paths can't be referenced from a TS config, so init names them in a warning.
  
  A JSON config that doesn't parse is left in place, and neither file is written.
  
  Re-running `ultracite init` also keeps what you added to `oxlint.config.ts` and `oxfmt.config.ts`: custom `rules`, `overrides`, `ignorePatterns`, `settings` and other properties, extra `extends` entries, your own imports and statements, and comments are carried over while the Ultracite parts are regenerated. Before, both files were regenerated from scratch. A config that doesn't parse is now left unchanged with a warning instead of being overwritten.
  
  `ultracite doctor` now fails when a JSON config and a TS config for Oxlint or oxfmt sit side by side, and suggests running `init` to migrate a lone `.oxfmtrc.json`.
- 981ef2f: `ultracite init --linter oxlint` no longer adds `"type": "module"` to `package.json`. That field changes how Node loads every `.js` file in the package, so CommonJS files such as a `next.config.js`, `postcss.config.js` or `jest.config.js` using `module.exports` stopped working after init.
  
  Instead, init writes the Oxlint and oxfmt configs as `oxlint.config.mts` and `oxfmt.config.mts` when the package isn't an ES module package (no `"type"` or `"type": "commonjs"`). A `.mts` file always loads as an ES module, with no `MODULE_TYPELESS_PACKAGE_JSON` warning on every run, and it works under `"type": "commonjs"`. ES module packages (`"type": "module"`) still get `oxlint.config.ts` and `oxfmt.config.ts`.
  
  Re-running init updates an existing config under the name it already has. The one exception is a `.ts` config in a `"type": "commonjs"` package, which Node can't load: init renames it to `.mts` and says so. `ultracite doctor`, linter detection and the stale-config cleanup all recognise the `.mts` names. `doctor` fails a `.ts` config in a CommonJS package, and fails when a `.ts` and an `.mts` config sit side by side.
  
  Requires oxfmt >= 0.59.0, the first release that finds `oxfmt.config.mts` on its own.
- 9a9beb0: `ultracite init` is gentler with `package.json`:
  
  - It keeps the file's key order, indentation and line endings. Before, every write moved `devDependencies` to the top and pushed keys like `private`, `main` and `exports` to the end.
  - It no longer overwrites a `check` or `fix` script the project already has (for example `"check": "tsc --noEmit"`). It prints a warning instead, and it only adds the scripts that are missing. Existing scripts that already run Ultracite with extra flags are left as they are.
  - When switching linters, it only removes the previous linter's packages from `devDependencies`. Packages in `dependencies` and `peerDependencies` stay, such as `prettier` used at runtime or `eslint` as the peer of a published plugin.
  - With `--skip-install`, the final message now says the dependencies were added to `package.json` instead of claiming they were installed.
- c3027a5: Re-running `ultracite init` no longer silently discards changes to an ESLint, Prettier or Stylelint config that builds on Ultracite's:
  
  - `eslint.config.*`: the framework presets it already spreads are kept alongside any newly selected ones, together with your own config objects, imports and a `defineConfig(...)` wrapper. Before, the file was regenerated from the selected frameworks only, dropping earlier presets and every custom rule.
  - `prettier.config.*`: your options, imports and extra plugins are kept. Framework plugins are added, and `prettier-plugin-tailwindcss` stays last.
  - Stylelint: a config that already uses `ultracite/stylelint`, including one in `package.json`, is left as it is.
  
  A config that doesn't use Ultracite's presets is still replaced, but init now prints a warning naming the file (or the `package.json` key) it replaced. A config that doesn't parse is left unchanged with a warning. With the ESLint toolchain, a `"prettier"` or `"stylelint"` key in `package.json` is now handled by these updates (and reported when replaced) instead of being deleted silently during migration.
- 44d6997: `ultracite init` no longer creates directories for agent rule files or agent hook configs (such as `.claude/` or `.cursor/`) before checking that the path stays inside the project. When one of those directories was a symlink to somewhere outside the project, init created the missing directories there before it refused to write the file.
- 33a1ccd: `ultracite init --integrations husky --skip-install` no longer replaces an existing `prepare` script in `package.json`. It adds `husky` to the script, as the installing path already did (for example `"prepare": "svelte-kit sync && husky"`). The Lefthook setup messages now name the config file init actually updates or creates, such as `.lefthook.yaml`, instead of always saying `lefthook.yml`.
- 923667b: `ultracite init` no longer overrides a tsconfig that turns `strictNullChecks` off. It leaves an explicit `"strictNullChecks": false`, whether set in the file or in a config it extends, as it is and prints a warning. It also follows `extends`, whether a relative path or a package such as `@tsconfig/strictest`, so a tsconfig that already inherits `strict` or `strictNullChecks` is no longer given a redundant `"strictNullChecks": true`.
- 199974c: `ultracite init` and `ultracite upgrade` now work in Yarn 2+ monorepos. The root install used to pass Yarn 1's `-W` flag, which Yarn 2+ rejects with "Unsupported option name", whenever the Yarn version wasn't named in `package.json`'s `packageManager` field. That happened with `--pm yarn`, with lockfile-only detection, and always in the second half of `upgrade`, which re-ran the new CLI with a bare `--pm yarn`. Ultracite now tells Yarn 2+ apart from Yarn 1 by `.yarnrc.yml` or the lockfile format. `upgrade` only forwards `--pm` to the newly installed CLI when you passed it.
  
  `ultracite doctor` (and the check at the end of `upgrade`) no longer fails in Yarn Plug'n'Play projects just because it can't find Ultracite or the tools in `node_modules`. It warns that it can't verify them there instead.
- c33158d: Agent hooks set up by `ultracite init --hooks` work in more places and no longer double up.
  
  - **GitHub Copilot:** `.github/hooks/ultracite.json` is now written in the format the Copilot CLI and Copilot cloud agent read: `"version": 1` and a camelCase `postToolUse` event. VS Code reads this format too. The previous file, with a PascalCase `PostToolUse` and no `version`, was only picked up by VS Code. Re-running init moves an existing Ultracite hook to the new format instead of keeping both.
  - **`fix --hook` with Copilot:** the file path is now read from Copilot payloads: `toolArgs.path` from the Copilot CLI (including when `toolArgs` is a JSON string), `tool_input.filePath` from VS Code's edit tools, every file of a `multi_replace_string_in_file` edit, and the file headers of an `apply_patch`. So only the edited files are fixed, not the whole project. These hosts run the hook after every tool, so tools that edit nothing (such as `read_file`, `view` or `bash`) now skip the fix instead of fixing the whole project.
  - **Re-running init no longer adds a second hook** next to one generated by Ultracite 7.8.2 or earlier: `pnpm fix …`, `bun fix …`, `yarn fix …`, or `npm run fix --skip=…`. That older hook is updated in place, so it stops running a whole-project fix after every edit.
  - **A hook is now added** even when the settings file mentions `ultracite` somewhere else, such as a Claude Code permission rule like `Bash(npx ultracite check)`. Only existing hook commands are checked.
  - `fix --hook` now fixes a file at the project root whose name starts with `..`, instead of treating it as outside the project.
  - **Zed:** the `includePackageJsonAutoImports` preference is now set for vtsls, Zed's default TypeScript and JavaScript language server (`lsp.vtsls.settings`), for both TypeScript and JavaScript. It was previously set under `typescript-language-server`, which Zed doesn't use by default.
- dc5e18e: Re-running `ultracite init` now updates the Ultracite rules in `AGENTS.md`, `.claude/CLAUDE.md`, `GEMINI.md` and the other shared rule files in place, instead of appending another full copy.
  
  - Previously a copy was appended whenever the linter, the package manager or the rules text changed. For example, switching from Biome to Oxlint left instructions for both engines side by side. Duplicate copies written by earlier versions are now removed, and your own content before and after the block is kept.
  - Files with CRLF line endings keep them, and are no longer duplicated on every run.
  - The commands in the rules now run the project's installed CLI (`npx ultracite fix`, `yarn ultracite fix`, `pnpm exec ultracite fix`, `bunx ultracite fix`) instead of a dlx runner. For Deno they read `deno run -A npm:ultracite fix`; previously the broken `deno run -A npm: ultracite fix` was written.
  - **Firebender:** rules are now written to `.firebender/rules/ultracite.mdc` with `alwaysApply: true`, where Firebender reads project rules. They were previously written as Markdown into `firebender.json`, which made that file invalid JSON and overwrote an existing Firebender config. A `firebender.json` that still holds only those Markdown rules is reset to `{}`.
- 2ad7704: Fixes for `ultracite fix --claude` and `ultracite fix --codex`:
  
  - On a terminal, the ✓/✗ result for every issue is now kept once a file is done. Previously a file with more issues than the terminal had rows kept only the last screenful of results.
  - In CI logs and other piped output, lint messages are no longer cut off at 80 columns.
  - The prompt is now sent to the agent CLI on stdin (`claude -p`, `codex exec -`) instead of as a command-line argument. On Windows, an npm-installed CLI runs through a `.cmd` shim, which can cut an argument off at its first newline, so the agent received only the prompt's first line and never saw the file or its issues.
  - `--codex` now passes `--skip-git-repo-check`, so it also works in a project that isn't a git repository. Previously `codex exec` refused to run there and every file failed.
- 0b0775e: The Husky pre-commit hook written by `ultracite init --integrations husky` is more robust.
  
  - The hook now runs the project's installed tools (`npx`, `yarn`, `pnpm exec`, `bunx`) instead of `yarn dlx` / `pnpm dlx`. `yarn dlx` does not exist in Yarn 1, so every commit failed with "Ultracite found issues that could not be auto-fixed", and `pnpm dlx` downloaded the latest release instead of the version the project pins. Husky itself is also initialised with the installed binary.
  - Installing Husky now chains `husky` onto an existing `prepare` script (for example SvelteKit's `svelte-kit sync || echo ''`) instead of replacing it. Choosing Husky and lefthook together no longer leaves only one of them in `prepare`.
  - When nothing is staged, the hook no longer exits early, so commands you keep after the Ultracite section still run.
  - Re-running init replaces the Ultracite section of a hook saved with CRLF line endings instead of appending a second one. It also no longer drops the last line of a hook that only mentions "# ultracite" in a comment. Commands after a lint-staged section written by an older version are kept.
- 3862221: `ultracite init --integrations lint-staged` no longer damages or shadows an existing lint-staged config.
  
  - An extension-less `.lintstagedrc` written in YAML (which is how lint-staged reads it) is now edited as YAML. Previously it was overwritten with a JSON file holding only the Ultracite task, deleting the user's own tasks, and single-quoted YAML was silently skipped.
  - YAML configs are edited through the `yaml` Document API, so comments and the rest of the file are kept.
  - TypeScript configs (`.lintstagedrc.ts`, `.mts`, `.cts` and `lint-staged.config.ts`, `.mts`, `.cts`) and the `lint-staged` key of `package.yaml` are now recognised. Previously init created a `.lintstagedrc.json` next to them, and because lint-staged uses only the first config in a directory, the user's config silently stopped running.
  - When a config can't be edited automatically (a function-based entry, a CommonJS module that can't be loaded, a syntax error), init now leaves it untouched and prints the command to add, instead of writing a second config file that would replace it.
  - When both exist, the dedicated config file is updated rather than the `package.json` key, matching the file lint-staged actually uses.
  - A single command already mapped to the same glob is kept alongside `ultracite fix` instead of being replaced.
  - The task now runs the project's installed Ultracite (`npx`, `yarn`, `pnpm exec`, `bunx`) instead of `yarn dlx` / `pnpm dlx`, which don't exist in Yarn 1 or download the latest release instead of the pinned one. Re-running init upgrades a `dlx` command written by an earlier version.
- bffb8dd: Corrected the bundled Ultracite agent skill (`skills/ultracite/SKILL.md`):
  
  - It said Oxlint has `github` and `sonarjs` presets that init includes by default. Neither exists. The GitHub, SonarJS and React Doctor rules live in the `ultracite/oxlint/js-plugins` preset (plus `next/js-plugins` and `tanstack/js-plugins`), next to the `anti-slop` and `shadcn` presets, and all of them are opt-in.
  - The `--js-plugins` init flag is now documented.
  - The skill now tells agents to run the project's installed CLI (`npx`, `pnpm exec`, `yarn`, `bunx`) rather than `pnpx` or `yarn dlx`.
- da2934a: Installing the Ultracite skill from `ultracite init` now works when init runs in a normal terminal.
  
  Init runs `skills add haydenbleasel/ultracite` without a terminal attached. When the `skills` CLI needed to ask which agents to install to, or whether to install into the project or globally, it could not show the prompt:
  
  - In some cases it cancelled the prompt and exited successfully with nothing installed, so init said "Ultracite skill installed." when the skill wasn't there.
  - In others it failed, so the install never happened.
  
  Init now passes `--yes` and names the agents to install for, so `skills` installs without asking. The skill always goes to the shared `.agents/skills` directory, which Codex, Cursor, GitHub Copilot, Gemini CLI, Amp, Cline, OpenCode and other agents read. It also goes to the skills directory of any agent whose project folder exists (for example `.claude`, `.windsurf`, `.codebuddy` or `.roo`, which init creates when you choose those agents).
  
  Init names the agents because otherwise, in a project where `skills` detects no agent, `--yes` would install for every agent it knows. That also created a `.claude/skills` link and a second copy in a top-level `agent/` directory (for the Eve framework). The "install it later" hint still shows the interactive `skills add haydenbleasel/ultracite` command.
- 4941883: `ultracite init` now edits `.pre-commit-config.yaml` and lefthook configs through the `yaml` Document API instead of splicing text, so the result is always valid YAML and comments are kept.
  
  - **pre-commit:** a `repos:` list written flush with its key (`-   repo:`, the style `pre-commit sample-config` generates) or with four-space indentation no longer turns into invalid YAML that breaks every commit. The file's sequence style is kept.
  - **lefthook:** a four-space indented `pre-commit:` block, a compact `jobs:` list, or a column-0 comment inside the block no longer produce invalid YAML or a duplicate `jobs:` key. A hook that uses `commands:` gets a `jobs:` list next to it.
  - **lefthook:** init now finds an existing `.lefthook.yml`, `lefthook.yaml`, `.config/lefthook.yml` (and the other names lefthook reads) and edits it. Previously it created `lefthook.yml`, which lefthook picks first, so the user's own config silently stopped running. A JSON or TOML config is left alone with instructions.
  - **lefthook:** the job's globs are now `*.js`, `*.ts`, and so on. With lefthook's default matcher, the previous `**/*.js` form skipped root-level files such as `package.json` or `index.ts`. Projects that set `glob_matcher: doublestar` get `**/*.js`.
  - **lefthook:** re-running init with a different package manager updates the existing job instead of adding a second one.
  - Both now run the project's installed Ultracite (`npx`, `yarn`, `pnpm exec`, `bunx`) instead of `yarn dlx` / `pnpm dlx`. Re-running init upgrades a hook or job written by an earlier version.
  - `lefthook install` runs the project's installed lefthook, and the `prepare` script is chained onto an existing one (e.g. `svelte-kit sync || echo '' && lefthook install`) instead of replacing it.
- 9c30c15: Raise the `oxlint` peer dependency to `^1.82.0`. The core Oxlint preset configures `no-unmodified-loop-condition` with `checkConditionalExpressions`, an option Oxlint 1.79 to 1.81 reject, so those releases failed with "Failed to parse oxlint configuration file" even though the old `^1.79.0` range allowed them. `ultracite doctor` and your package manager now flag the too-old version instead.
  
  Requires oxlint >= 1.82.0
- 47303f4: The Biome `jest` preset now declares Jest's globals (`describe`, `it`, `test`, `expect`, `jest`, `beforeEach` and the rest of `globals.jest`) for test files. Jest injects them without imports, so `noUndeclaredVariables` from the core preset reported every one of them in idiomatic Jest tests. The ESLint `jest` preset already declared them.
  
  The route-file exemptions in the Biome `remix` and `tanstack` presets (`useFilenamingConvention` off in `routes/`) now cover `.js` and `.jsx` route files as well as `.ts` and `.tsx`.
- 8f236ee: The ESLint core preset now lints `.jsx`, `.tsx`, `.mts` and `.cts` files. Its main block previously matched only `.js`, `.mjs`, `.cjs`, `.ts`, `.json` and `.html`, so ESLint skipped `.mts` and `.cts` files entirely ("File ignored because no matching configuration was supplied"), and `.jsx` and `.tsx` files received only the framework preset's rules. `.tsx` files were also parsed by espree, so any TypeScript syntax in them was a parse error. Every TypeScript extension now uses `@typescript-eslint/parser` with type information, and the core rules apply to all of them.
  
  Type information now comes from the TypeScript project service (`parserOptions.projectService: true`) instead of a fixed `project: "./tsconfig.json"`. Solution-style tsconfigs (`"files": []` plus `references`, as in the Vite templates) and monorepo packages now resolve their own tsconfig, where previously every file failed to parse. A file that no tsconfig includes still has to be added to one; typescript-eslint reports it as "not found by the project service" (see https://typescript-eslint.io/troubleshooting/typed-linting/#i-get-errors-telling-me-was-not-found-by-the-project-service-consider-either-including-it-in-the-tsconfigjson-or-including-it-in-allowdefaultproject).
  
  The JavaScript block no longer matches `**/*.json`. espree cannot parse JSON, so every `package.json` and `tsconfig.json` failed with `Parsing error: Unexpected token :`. The ESLint preset does not lint JSON, matching the Oxlint preset.
- 94722fa: The `eslint.config.mjs` that `ultracite init` writes no longer fails its own lint. Every ESLint preset exported a default named `config`, so `import core from "ultracite/eslint/core"` (and each framework import) tripped `import-x/no-rename-default` twice per preset. Each preset's default export is now named after the identifier the generated config imports it as (`core`, `react`, `tanstack`, …).
- d9803a6: Fix ESLint presets that crashed the whole run or never reached the files they target.
  
  - **TanStack:** `@tanstack/start/no-async-client-component` and `@tanstack/start/no-client-code-in-server-component` need type information and aborted ESLint ("You have used a rule which requires type information") on the first `.jsx` or `.tsx` file. They now run only on TypeScript files. The Query, Router and Start rules also apply to plain `.ts`/`.js` modules, where query and mutation options are usually declared, instead of only to `.jsx` and `.tsx`. `sort-keys` is off, and `no-use-before-define` and `unicorn/filename-case` are off in `routes/` directories, matching the Oxlint and Biome TanStack presets (alphabetical option keys break TanStack's type inference, and file routes are mutually recursive and encode the URL in the filename).
  - **Qwik:** `qwik/valid-lexical-scope` needs type information and crashed ESLint on `.jsx` files. It now runs only on `.tsx` files.
  - **Jest:** `jest/no-deprecated-functions` threw "Unable to detect Jest version" and crashed ESLint when the `jest` package was not resolvable from the plugin, for example in bun:test projects. The preset now reads the Jest version from the project and falls back to the latest major.
  - **Cypress:** the block used `globals.cypress`, which does not exist in the `globals` package, so `cy`, `Cypress`, `describe` and `it` were reported by `no-undef`. It now uses `eslint-plugin-cypress`'s own globals config. It also matched only `*.cy.js`, and now covers `*.cy.*` for every JS and TS extension plus files under `cypress/`.
  - **Storybook:** the block matched only `*.stories.js` and `*.stories.ts`. It now covers `*.stories.*` and `*.story.*` for every JS and TS extension, including `.tsx` and `.jsx`.
- d6ee272: The ESLint `vue`, `svelte` and `astro` presets now parse their files. They registered only the plugin and its rules, never a parser, so ESLint read every `.vue`, `.svelte` and `.astro` file with espree and reported a parse error (`Unexpected token <`) instead of linting it.
  
  Each preset now starts from its plugin's own base config (`flat/base` in `eslint-plugin-vue`, `eslint-plugin-svelte` and `eslint-plugin-astro`), which registers `vue-eslint-parser`, `svelte-eslint-parser` or `astro-eslint-parser` from the plugin's dependencies, and parses `<script lang="ts">` blocks with `@typescript-eslint/parser`. The Svelte preset also covers `.svelte.js` and `.svelte.ts` rune modules. In Astro files, client-side `<script>` tags are linted without type information, because those in-memory virtual files cannot be type-checked.
- f7599ef: The ESLint core preset no longer re-enables formatting rules that `eslint-config-prettier` turns off. The unicorn rule set was spread after `eslint-config-prettier`, which switched `unicorn/number-literal-case`, `unicorn/template-indent` and `unicorn/empty-brace-spaces` back on. `unicorn/number-literal-case` demands `0xABCD` while Prettier prints `0xabcd`, so `prettier/prettier` and the unicorn rule reported every hex literal and `eslint --fix` could not satisfy both. `eslint-config-prettier` now applies after every plugin, so `unicorn/number-literal-case` and `unicorn/template-indent` are off. `unicorn/empty-brace-spaces` stays on (Prettier prints empty braces as `{}` too), matching the Oxlint preset.
- efa80e8: The ESLint core preset no longer reports the same problem two or three times on TypeScript files, and no longer flags ambient type-only globals.
  
  - `no-undef` and `sonarjs/no-reference-error` are off for `.ts`, `.tsx`, `.mts` and `.cts` files. TypeScript already reports unresolved identifiers, and both rules flagged valid ambient types such as `NodeJS.Timeout` (see the [typescript-eslint FAQ](https://typescript-eslint.io/troubleshooting/faqs/eslint#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors)).
  - The base `class-methods-use-this`, `consistent-return`, `no-unused-private-class-members`, `prefer-destructuring` and `prefer-promise-reject-errors` rules are off for TypeScript files, because their `@typescript-eslint` extension rules are enabled there. The base rules duplicated every report and ignored the extensions' TypeScript-aware options.
  - `unused-imports/no-unused-vars` is off. It is a copy of `no-unused-vars` / `@typescript-eslint/no-unused-vars`, which stay on as in the Oxlint preset, so every unused variable was reported twice. `unused-imports/no-unused-imports` stays on for its autofix.
  - `@typescript-eslint/max-params` is off, matching `max-params` being off in the core presets. It was on with its default limit of 3 parameters for TypeScript files only.
- 9ae95d1: Fix false positives in the ESLint `vue`, `svelte` and `astro` presets that appeared once their files were actually parsed.
  
  - `vue/block-lang` and `svelte/block-lang` rejected every `<script lang="ts">`, because with no options they only allow blocks without a `lang` attribute. They now allow TypeScript scripts, and `less` and `scss` styles, as well as blocks without `lang`.
  - Astro's client-side `<script>` tags are linted as virtual files named `1_1.ts`, `2_1.js` and so on, which `github/filenames-match-regex` reported on every page with a script tag. Filename rules are now off for those virtual files.
  - The Astro preset passes `@typescript-eslint/parser` for the frontmatter explicitly. ESLint's config merge dropped the copy in `eslint-plugin-astro`'s base config, so frontmatter TypeScript parsed only when `@typescript-eslint/parser` happened to resolve from the working directory, and failed with `The keyword 'interface' is reserved` otherwise.
  - `ultracite init` now installs `eslint-plugin-jsx-a11y` for Astro projects using ESLint. The preset enables `eslint-plugin-astro`'s `astro/jsx-a11y/*` rules, which load that package and reported "you need to install eslint-plugin-jsx-a11y" on every `.astro` file when it was missing.
- e88e76d: The Prettier preset now sets `tailwindFunctions` to `clsx`, `cva`, `tw`, `twMerge`, `cn`, `twJoin` and `tv`, so `prettier-plugin-tailwindcss` sorts Tailwind classes passed to those helpers anywhere in a file, for example `const button = cva("p-4 flex")`. Previously only class strings inside `class`/`className` attributes were sorted with Prettier, while the Oxfmt (`sortTailwindcss.functions`) and Biome (`useSortedClasses`) presets already sorted these helpers. See https://github.com/tailwindlabs/prettier-plugin-tailwindcss#sorting-classes-in-function-calls.
- 3a5885e: `github/filenames-match-regex` now accepts file-based routing filenames in the ESLint preset too, and Next.js's special pages in both presets.
  
  - The page-route regex (Oxlint `js-plugins` and now ESLint `core`) allows a leading underscore, so the Next.js pages-router files `_app`, `_document` and `_error`, which cannot be renamed, are no longer reported. Other names in `pages/` (`BadPage.ts`) and camelCase route params (`[postId]`, which `unicorn/filename-case` rejects too) are still reported.
  - The ESLint `core` preset now carries the same route exemptions as the Oxlint `js-plugins` preset (#799, #804). Previously it applied the plain kebab-case regex everywhere and reported SvelteKit's `+page.ts` and `+server.ts`, TanStack Router and React Router files such as `posts.$postId.ts`, and page routes such as `[slug].json.ts` and `[...auth].ts`.
  - The route-directory exemptions (`github/filenames-match-regex` in Oxlint `js-plugins` and ESLint `core`, plus `unicorn/filename-case` and `no-use-before-define` in the Oxlint and ESLint `tanstack` presets) now cover `.js` and `.jsx` route files as well as `.ts` and `.tsx`.
- ad2adb0: The Stylelint preset now handles Tailwind CSS v4, SCSS and Less, and skips build output.
  
  - **Tailwind CSS v4:** `@theme`, `@utility`, `@variant`, `@custom-variant`, `@slot`, `@plugin` and `@config` are allowed by `at-rule-no-unknown`, alongside the Tailwind v3 at-rules and `@source` and `@reference`. `at-rule-prelude-no-invalid` no longer rejects every `@apply` (or the other Tailwind at-rules' preludes). `custom-property-pattern` accepts theme namespace resets (`--color-*: initial`) and sub-properties (`--text-xl--line-height`). `declaration-property-value-no-unknown` and `function-no-unknown` accept `--spacing()`, `--alpha()`, `--value()`, `--modifier()` and `theme()`. `media-query-no-invalid` accepts `theme()` breakpoints, and `nesting-selector-no-missing-scoping-root` allows `&` inside `@custom-variant`, `@variant` and `@utility`. See https://tailwindcss.com/docs/functions-and-directives.
  - **`import-notation`** is now `"string"`. `stylelint-config-standard`'s `"url"` autofix rewrote `@import "tailwindcss"` to `@import url("tailwindcss")`.
  - **SCSS and Less:** `.scss` files are parsed with `postcss-scss` and `.less` files with `postcss-less`. Previously every SCSS and Less file failed with `CssSyntaxError` (for example on `//` comments or mixin calls). The CSS-only rules that cannot understand preprocessor syntax (unknown at-rules such as `@use`, `@include` and `@mixin`, `$variable` and `@variable` values, Sass and Less built-in functions, `//` comments) are relaxed for those files. `ultracite init` now installs `postcss-scss` and `postcss-less` with the ESLint toolchain.
  - **`.sass`:** the indented Sass syntax has no maintained PostCSS parser, so `ultracite check` and `ultracite fix` no longer pass `.sass` files to Stylelint, where every one failed to parse.
  - **Ignores:** `ignoreFiles` now uses the shared ignore list (`dist`, `build`, `.next`, `coverage`, `storybook-static` and so on). Stylelint does not read `.gitignore`, so it previously linted generated CSS.
  - **Prettier:** `prettier/prettier` is now enabled. The preset loaded `stylelint-prettier` but never turned its rule on.
  - `ultracite check` and `ultracite fix` now escape glob characters in directory arguments before passing them to Stylelint. A directory such as `app/(marketing)` previously matched no files.
- 89989df: Test-file overrides in every preset now match `.mts`, `.cts`, `.mjs` and `.cjs` test files as well as `.ts`, `.tsx`, `.js` and `.jsx`. The globs were `**/*.{test,spec,test-d,spec-d}.{ts,tsx,js,jsx}` and `**/__tests__/**/*.{ts,tsx,js,jsx}`, so a `sum.test.mts` file got none of the test-file relaxations and none of the Jest or Vitest rules. For example, `test.only` in a `.test.mts` file was not reported by `vitest/no-focused-tests` or `jest/no-focused-tests`. This covers the Oxlint `core`, `js-plugins`, `jest` and `vitest` presets, the Biome `core` preset, and the ESLint `core`, `jest` and `vitest` presets.
- 4d28cf9: The Oxlint `vue` preset now lists every non-nursery Vue rule Oxlint implements, like the other framework presets. It previously enabled 18 of them. Among the 27 newly enabled rules are `vue/valid-define-props`, `vue/valid-define-emits`, `vue/no-export-in-script-setup`, `vue/no-lifecycle-after-await`, `vue/no-arrow-functions-in-watch`, `vue/no-side-effects-in-computed-properties`, `vue/require-typed-ref`, `vue/define-props-destructuring` and the `vue/no-deprecated-*` rules. `vue/max-props` is off in both the Oxlint and ESLint presets, because its default allows a single prop per component.
  
  The Oxlint `vitest` preset now enables `vitest/consistent-test-it`, the one Vitest rule it was missing (the ESLint preset already had it).
  
  Vue files whose names Nuxt and file-based Vue Router require (`pages/**`, `layouts/**`, `error.vue` and `app.vue`) are exempt from `useVueMultiWordComponentNames` in the Biome preset and from `vue/multi-word-component-names` in the ESLint preset. Previously `pages/index.vue`, `layouts/default.vue` and `error.vue` were always reported.
