docs/plans/2026-03-05-eslint-prettier-to-oxlint-oxfmt.md
Status: Executed 2026-03-05 — path references reflect pre-rename interrupts-langraph spelling; the rename to interrupts-langgraph happened after this plan ran.
Note on paths: This plan was written before
examples/v2/interrupts-langraphwas renamed tointerrupts-langgraph(spelling fix). Path references below intentionally preserve the pre-rename name as a historical record of what was touched when this migration ran; they are NOT a source of truth for the current tree layout.
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Replace ESLint and Prettier with oxlint and oxfmt for faster linting and formatting across the entire monorepo.
Architecture: Single root-level .oxlintrc.json and .oxfmtrc.json replace all per-project ESLint configs and
scattered Prettier configs. The custom require-cpk-prefix rule is preserved as an oxlint JS plugin. All package.json
scripts, CI workflows, and git hooks are updated to use the new tools.
Tech Stack: oxlint (linting), oxfmt (formatting), pnpm, Nx
Files:
package.json (root)Step 1: Add oxlint and oxfmt as root devDependencies
pnpm add -D oxlint oxfmt -w
Step 2: Remove ESLint and Prettier root devDependencies
pnpm remove eslint prettier prettier-plugin-tailwindcss eslint-config-custom -w
Step 3: Remove ESLint devDependencies from v2/eslint-config
pnpm remove @eslint/js eslint eslint-config-prettier eslint-plugin-only-warn eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-turbo globals typescript-eslint @next/eslint-plugin-next --filter @copilotkitnext/eslint-config
Step 4: Remove ESLint devDependencies from v1/eslint-config-custom
pnpm remove eslint-config-next eslint-config-prettier eslint-plugin-react eslint-config-turbo --filter eslint-config-custom
Step 5: Remove any ESLint dependencies from example packages that have them
Check and remove eslint-related deps from:
examples/v1/research-canvas/package.jsonexamples/v2/react/demo/package.jsonexamples/v2/interrupts-langraph/apps/web/package.jsonStep 6: Verify install
pnpm install --frozen-lockfile || pnpm install
npx oxlint --version
npx oxfmt --version
Expected: Both print version numbers.
Step 7: Commit
chore: add oxlint + oxfmt, remove eslint + prettier deps
Files:
.oxfmtrc.json (root).oxlintrc.json (root)packages/v2/react/eslint-rules/copilotkit-plugin.mjs (oxlint JS plugin wrapper)Step 1: Run oxfmt's prettier migration
npx oxfmt --migrate=prettier
This generates .oxfmtrc.json from the existing prettier config. Review and adjust the output. The target config should
look like:
{
"printWidth": 120,
"proseWrap": "always"
}
If --migrate=prettier doesn't pick up the right config (there are multiple .prettierrc files in subdirs, not at
root), create it manually with the V2 settings above.
Step 2: Create root .oxlintrc.json
{
"$schema": "https://raw.githubusercontent.com/nicolo-ribaudo/oxlint-json-schema/refs/heads/main/.oxlintrc.json",
"plugins": ["typescript", "unicorn", "oxc", "react", "nextjs", "import"],
"jsPlugins": ["./packages/v2/react/eslint-rules/copilotkit-plugin.mjs"],
"categories": {
"correctness": "warn",
"suspicious": "warn"
},
"rules": {
"copilotkit/require-cpk-prefix": "warn"
},
"overrides": [
{
"files": ["**/__tests__/**", "**/*.test.*", "**/*.spec.*"],
"rules": {
"copilotkit/require-cpk-prefix": "off"
}
}
],
"ignorePatterns": [
"dist/**",
"node_modules/**",
".next/**",
".nuxt/**",
"coverage/**",
"storybook-static/**",
"**/@generated/**",
"docs/**"
]
}
Step 3: Create the oxlint JS plugin wrapper
Create packages/v2/react/eslint-rules/copilotkit-plugin.mjs:
import requireCpkPrefix from "./require-cpk-prefix.mjs";
const plugin = {
meta: { name: "copilotkit" },
rules: {
"require-cpk-prefix": requireCpkPrefix,
},
};
export default plugin;
The existing require-cpk-prefix.mjs already exports the rule in ESLint-compatible format — it should work with
oxlint's JS plugin support as-is.
Step 4: Verify oxlint loads the config
npx oxlint --print-config
Expected: Prints merged config showing the plugins and rules.
Step 5: Verify oxfmt works
npx oxfmt --check .
Expected: Runs without crashing (may report formatting differences — that's fine).
Step 6: Commit
chore: add oxlint and oxfmt configuration files
Files:
package.json (root — scripts)packages/v2/agent/package.jsonpackages/v2/angular/package.jsonpackages/v2/core/package.jsonpackages/v2/demo-agents/package.jsonpackages/v2/react/package.jsonpackages/v2/runtime/package.jsonpackages/v2/shared/package.jsonpackages/v2/sqlite-runner/package.jsonpackages/v2/voice/package.jsonpackages/v2/web-inspector/package.jsonexamples/v1/chat-with-your-data/package.jsonexamples/v1/state-machine/package.jsonexamples/v2/react/demo/package.jsonscripts/qa/lib/firebase/package.jsonnext lintStep 1: Update root package.json scripts
Change:
{
"lint": "nx run-many -t lint --projects=packages/**",
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
"check-prettier": "prettier --check \"**/*.{ts,tsx,md}\""
}
To:
{
"lint": "nx run-many -t lint --projects=packages/**",
"format": "oxfmt --write .",
"check-format": "oxfmt --check ."
}
Note: The check-prettier script is renamed to check-format for consistency. The CI workflow will be updated to match
in Task 5.
Step 2: Update all V2 package.json lint scripts
For all packages in packages/v2/*/package.json, change:
"lint": "eslint ."
To:
"lint": "oxlint ."
Special case — packages/v2/voice/package.json has:
"lint": "eslint . --max-warnings 0"
Change to:
"lint": "oxlint . --deny-warnings"
Step 3: Update example package.json lint scripts
For examples with "lint": "eslint ." or "lint": "eslint \"src/**/*.{js,jsx,ts,tsx}\" \"*.{js,cjs,mjs,ts}\"": Change
to:
"lint": "oxlint ."
For examples with "lint": "next lint": Change to:
"lint": "oxlint ."
For scripts/qa/lib/firebase/package.json:
"lint": "eslint --ext .js,.ts ."
Change to:
"lint": "oxlint ."
Step 4: Verify lint runs
npx oxlint .
Expected: Runs and outputs lint results (warnings/errors are fine for now — we just need it to not crash).
Step 5: Commit
chore: update all lint/format scripts to use oxlint/oxfmt
Files to DELETE:
V2 eslint config package (entire directory):
packages/v2/eslint-config/package.jsonpackages/v2/eslint-config/base.jspackages/v2/eslint-config/react-internal.jspackages/v2/eslint-config/next.jsV1 eslint config package (entire directory):
packages/v1/eslint-config-custom/package.jsonpackages/v1/eslint-config-custom/index.jsV2 package eslint configs:
packages/v2/agent/eslint.config.mjspackages/v2/angular/eslint.config.mjspackages/v2/core/eslint.config.mjspackages/v2/demo-agents/eslint.config.mjspackages/v2/react/eslint.config.mjspackages/v2/runtime/eslint.config.mjspackages/v2/shared/eslint.config.mjspackages/v2/sqlite-runner/eslint.config.mjspackages/v2/voice/eslint.config.mjspackages/v2/web-inspector/eslint.config.mjsV1 package eslint configs:
packages/v1/runtime/.eslintrc.jspackages/v1/a2ui-renderer/eslint.config.jsExample eslint configs:
examples/v1/next-openai/.eslintrc.jsexamples/v1/next-pages-router/.eslintrc.jsexamples/v1/node-express/.eslintrc.jsexamples/v1/node-http/.eslintrc.jsexamples/v1/research-canvas/.eslintrc.jsonexamples/v1/travel/.eslintrc.jsonexamples/v1/chat-with-your-data/eslint.config.mjsexamples/v1/form-filling/eslint.config.mjsexamples/v1/state-machine/eslint.config.mjsexamples/v1/_legacy/copilot-anthropic-pinecone/.eslintrc.jsonexamples/v1/_legacy/copilot-openai-mongodb-atlas-vector-search/.eslintrc.jsonexamples/v1/_legacy/copilot-fully-custom/eslint.config.mjsexamples/v2/react/demo/eslint.config.mjsexamples/v2/interrupts-langraph/eslint.config.mjsexamples/v2/interrupts-langraph/apps/web/eslint.config.mjsOther eslint configs:
src/v1.x/.eslintrc.jsdocs/eslint.config.mjsPrettier configs:
src/v1.x/.prettierrcsrc/v2.x/.prettierrcsrc/v1.x/.prettierignoredocs/.prettierignoreStep 1: Delete all files listed above
# V2 eslint-config package
rm -rf packages/v2/eslint-config/
# V1 eslint-config-custom package
rm -rf packages/v1/eslint-config-custom/
# V2 package eslint configs
rm -f packages/v2/*/eslint.config.mjs
# V1 package eslint configs
rm -f packages/v1/runtime/.eslintrc.js
rm -f packages/v1/a2ui-renderer/eslint.config.js
# Example eslint configs (legacy format)
rm -f examples/v1/next-openai/.eslintrc.js
rm -f examples/v1/next-pages-router/.eslintrc.js
rm -f examples/v1/node-express/.eslintrc.js
rm -f examples/v1/node-http/.eslintrc.js
rm -f examples/v1/research-canvas/.eslintrc.json
rm -f examples/v1/travel/.eslintrc.json
rm -f examples/v1/_legacy/copilot-anthropic-pinecone/.eslintrc.json
rm -f examples/v1/_legacy/copilot-openai-mongodb-atlas-vector-search/.eslintrc.json
# Example eslint configs (flat format)
rm -f examples/v1/chat-with-your-data/eslint.config.mjs
rm -f examples/v1/form-filling/eslint.config.mjs
rm -f examples/v1/state-machine/eslint.config.mjs
rm -f examples/v1/_legacy/copilot-fully-custom/eslint.config.mjs
rm -f examples/v2/react/demo/eslint.config.mjs
rm -f examples/v2/interrupts-langraph/eslint.config.mjs
rm -f examples/v2/interrupts-langraph/apps/web/eslint.config.mjs
# Other
rm -f src/v1.x/.eslintrc.js
rm -f docs/eslint.config.mjs
# Prettier configs
rm -f src/v1.x/.prettierrc
rm -f src/v2.x/.prettierrc
rm -f src/v1.x/.prettierignore
rm -f docs/.prettierignore
Step 2: Remove the eslint-config packages from pnpm workspace
Check pnpm-workspace.yaml — if these packages are explicitly listed, remove them. They're likely picked up by glob
patterns like packages/v2/* so deleting the directories should suffice.
Step 3: Verify no eslint/prettier configs remain
find . -name ".eslintrc*" -not -path "*/node_modules/*" 2>/dev/null
find . -name "eslint.config.*" -not -path "*/node_modules/*" 2>/dev/null
find . -name ".prettierrc*" -not -path "*/node_modules/*" 2>/dev/null
find . -name ".prettierignore" -not -path "*/node_modules/*" 2>/dev/null
Expected: No results (all config files removed).
Step 4: Commit
chore: remove all eslint and prettier config files
Files:
.github/workflows/static_quality.ymllefthook.ymlStep 1: Update the CI workflow
Replace the prettier and eslint jobs in .github/workflows/static_quality.yml:
name: static / quality
on:
push:
branches: [main]
paths-ignore:
- "docs/**"
- "README.md"
- "examples/**"
- ".github/workflows/demos_preview.yml"
- ".github/workflows/release.yml"
- "packages/v1/**/package.json"
- "packages/v1/**/CHANGELOG.md"
- ".changeset/**"
pull_request:
branches: [main]
paths-ignore:
- "docs/**"
- "README.md"
- "examples/**"
- ".changeset/**"
env:
NODE_OPTIONS: "--max-old-space-size=4096"
jobs:
format:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: "10.13.1"
- name: Install uv
uses: astral-sh/setup-uv@v6
- name: Use Node.js 20
uses: actions/setup-node@v3
with:
node-version: 20.x
cache: "pnpm"
cache-dependency-path: "**/pnpm-lock.yaml"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run format check
run: pnpm run check-format
oxlint:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: "10.13.1"
- name: Setup uv
uses: astral-sh/setup-uv@v6
- name: Use Node.js 20
uses: actions/setup-node@v3
with:
node-version: 20.x
cache: "pnpm"
cache-dependency-path: "**/pnpm-lock.yaml"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run oxlint check
run: pnpm run lint
package-quality:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: "10.13.1"
- name: Install uv
uses: astral-sh/setup-uv@v6
- name: Use Node.js 20
uses: actions/setup-node@v3
with:
node-version: 20.x
cache: "pnpm"
cache-dependency-path: "**/pnpm-lock.yaml"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run publint and attw
run: pnpm run check:packages
Step 2: Update lefthook.yml
The pre-commit hook currently runs:
lint-fix:
tags: lint
run: pnpm run lint --fix && pnpm run format
stage_fixed: true
The pnpm run lint and pnpm run format scripts are already updated (Task 3), so the underlying tools change
automatically. However, oxlint uses --fix the same way, and oxfmt --write . is the default behavior, so
pnpm run format will work as-is.
No changes needed to lefthook.yml — the scripts it calls are already updated.
Step 3: Verify CI commands locally
pnpm run check-format
pnpm run lint
Expected: Both run without crashing.
Step 4: Commit
chore: update CI workflow for oxlint/oxfmt
Step 1: Search for any remaining references
grep -r "eslint" --include="*.json" --include="*.yml" --include="*.yaml" --include="*.js" --include="*.mjs" --include="*.ts" --include="*.md" . \
--exclude-dir=node_modules --exclude-dir=dist --exclude-dir=.next --exclude-dir=.git \
--exclude-dir=coverage -l
Look for:
eslint references in package.json devDependencies (should all be gone)eslint-disable comments in source code → change to oxlint-disable (or leave — oxlint may honor eslint-disable
comments for compatibility)grep -r "prettier" --include="*.json" --include="*.yml" --include="*.yaml" --include="*.js" --include="*.mjs" --include="*.ts" . \
--exclude-dir=node_modules --exclude-dir=dist --exclude-dir=.next --exclude-dir=.git \
--exclude-dir=coverage -l
Step 2: Check for eslint-disable comments in source
oxlint supports both eslint-disable and oxlint-disable comment directives. No changes strictly needed, but
optionally migrate them for consistency.
Step 3: Run pnpm install to clean up lockfile
pnpm install
Step 4: Verify everything works end-to-end
pnpm run lint
pnpm run check-format
Expected: Both commands complete successfully.
Step 5: Commit
chore: clean up remaining eslint/prettier references
Step 1: Run oxfmt to format the entire codebase
pnpm run format
This will reformat all files according to the new oxfmt config. Review the diff to ensure it looks reasonable.
Step 2: Run oxlint on the full codebase
pnpm run lint
Review any warnings/errors. Adjust .oxlintrc.json rules if needed (e.g., turn off noisy rules that weren't previously
enabled).
Step 3: Commit formatting changes separately
style: reformat codebase with oxfmt
Step 4: Final commit for any rule adjustments
If .oxlintrc.json needed tweaks:
chore: tune oxlint rules after initial run