Changelog
Release notes for the React Doctor CLI, read directly from the GitHub changelog.
0.2.10
Patch Changes
- Add Preact project detection so
react-doctor inspectrecognizes Preact workspaces, including Vite + Preact projects that still reportviteas their framework, and enables the bundled Preact rule family.
- Bundle new diagnostics across the rule set: Preact compatibility checks, HTML correctness and dialog accessibility rules,
hooks-no-nan-in-deps, Jotai atom diagnostics, React Native performance rules,js-async-reduce-without-awaited-acc, and React 19.2<Activity>effect-boundary checks.
- Fix CLI reliability around dead-code scans and setup prompts. Dead-code analysis now runs with a bounded worker path instead of freezing the scan, monorepo scans still show the setup prompt, and repeated setup questions collapse into one install flow.
- Inherit false-positive fixes for
control-has-associated-labelandno-giant-component.
- Dependency bump:
oxlint-plugin-react-doctor@0.2.10.
0.2.9
Patch Changes
- Publish workflow now uses npm trusted publishing through GitHub OIDC, including an npm version with provenance support. Releases no longer need a long-lived npm token.
- Dependency bump:
oxlint-plugin-react-doctor@0.2.9.
0.2.8
Patch Changes
- add react-doctor.config.json schema field
- Updated dependencies []:
- oxlint-plugin-react-doctor@0.2.8
0.2.7
Patch Changes
- Animated score progress bar. The CLI health score now renders with a smooth progress-bar animation, automatically skipped in CI and coding-agent environments.
- CI and agent detection. New
isCiOrAgentutility detects CI providers and coding agents (Cursor, Claude Code, Codex, etc.) and suppresses interactive prompts, animations, and the onboarding flow so scans run non-interactively where appropriate.
- Concurrent lint + dead-code analysis. The
inspectcommand now runs linting and dead-code detection in parallel instead of sequentially, reducing wall-clock scan time.
- Agent install hint. When running inside a coding agent, the CLI suggests
react-doctor installto set up the agent skill for in-editor diagnostics.
- Skip prefilled project question. The monorepo project-selection prompt is skipped when there is only one scannable project, removing an unnecessary interactive step.
- `/doctor` triage skill. The React Doctor agent skill now includes a
/doctorcommand that fetches the canonical playbook for full-project triage.
- Bundle
eslint-plugin-react-hooksas a direct dependency so React Compiler rules work out of the box.
- Updated dependencies []:
- oxlint-plugin-react-doctor@0.2.7
0.2.6
Patch Changes
- Remove
design-no-bold-headingrule - the heuristic produced too many false positives in design systems where headings intentionally vary weight.
- Updated dependencies []:
- oxlint-plugin-react-doctor@0.2.6
0.2.5
Patch Changes
- First-run onboarding. New users see a brief walkthrough on their first
react-doctorinvocation explaining what the tool does and how to read the report.
- Node 20 support. Fix runtime dependency resolution so the CLI runs correctly on Node 20 without requiring Node 22+ built-ins.
- Cover child workspace diff include paths so
--diffmode in monorepos correctly scans files changed inside nested workspace packages.
- Stop
jsx-keyfrom flagging shorthand JSX fragments (<>...</>) which cannot accept akeyprop.
- Normalize static template literal handling so rules treat `
hellothe same as"hello"`.
- Add
require-pnpm-hardeningenvironment check that warns whenpnpmis detected without strict lockfile settings.
- Updated dependencies []:
- oxlint-plugin-react-doctor@0.2.5
0.2.4
Patch Changes
- Effect v4 runtime migration. The entire scan pipeline is rebuilt on Effect v4 - tagged errors, dependency-injected services, generator-based control flow, and
Context.Referenceambient config replace the previous imperative architecture.
- New `@react-doctor/api` package. Programmatic
diagnose()entry point backed by the samerunInspectorchestrator the CLI uses, with typedReactDoctorErrorfailures andEffect.catchReasonsdispatch.
- `inspect()` rewired through `runInspect`. The CLI
inspectcommand now delegates to the core streaming orchestrator instead of managing the scan loop directly, aligning CLI and API behavior.
- Native agent hook installer.
react-doctor installwrites post-checkout / post-merge git hooks that auto-run the scan on relevant file changes.
- Opt-in OpenTelemetry.
REACT_DOCTOR_OTLP_ENDPOINT+REACT_DOCTOR_OTLP_AUTH_HEADERship every service span to an OTLP backend.
- User-plugin extension.
config.plugins: [...]loads custom oxlint plugin packages alongside the built-in rules.
- Security hardening. Pin CI workflow permissions, add fork guards, fix four pre-existing audit findings.
- Collapse
@react-doctor/typesand@react-doctor/project-infointo@react-doctor/core.
- Adopt
Effect.Consolethroughout - drop the customLoggerservice.
- Updated dependencies []:
- oxlint-plugin-react-doctor@0.2.4
0.2.3
Patch Changes
- Fix vite build configuration for bundling workspace dependencies so
npx react-doctorresolves internal workspace imports correctly.
- Updated dependencies []:
- oxlint-plugin-react-doctor@0.2.3
0.2.2
Patch Changes
- Restore
eslint-plugin-react-hooksas a hard dependency so React Compiler rules resolve without requiring users to install the peer separately.
(NickvanDyke, MIT) into oxlint-plugin-react-doctor. They now ship as react-doctor/* rules and no longer require the optional peer dependency. The optional peer-dep surface (effect/* rules, resolveYouMightNotNeedEffectPlugin, YOU_MIGHT_NOT_NEED_EFFECT_NAMESPACE) is removed from @react-doctor/core.
The ports use a real eslint-scope ScopeManager (cached per Program via WeakMap) - same references / resolved.defs[].node.init / isEventualCallTo chasing the upstream plugin uses. Diagnostic messages match upstream verbatim with template variables substituted in JS.
| Rule (now react-doctor/<id>) | What it catches | | ----------------------------------- | ------------------------------------------------------------------------ | | no-derived-state | Storing derived state via a useEffect instead of computing during render | | no-chain-state-updates | Chaining state updates across effects | | no-event-handler | Using state + a guarded effect as an event handler | | no-adjust-state-on-prop-change | Adjusting state in an effect when a prop changes | | no-reset-all-state-on-prop-change | Resetting all state in an effect (use a key prop) | | no-pass-live-state-to-parent | Pushing live state to a parent via a callback in an effect | | no-pass-data-to-parent | Passing fetched data to a parent via a callback in an effect | | no-initialize-state | Initializing state inside a mount-only effect |
Parity coverage: 195 of 196 upstream test cases pass (the 1 remaining case is upstream's own todo: true, "Set derived state via identical intermediate setter").
These coexist with React Doctor's existing thematically-related rules (no-derived-state-effect, no-effect-chain, no-event-trigger-state, no-prop-callback-in-effect) - different IDs, different shapes, different messages.
- Updated dependencies [`47772b7`]:
- oxlint-plugin-react-doctor@0.2.2
0.2.1
Patch Changes
- Make filesystem walks tolerate EPERM/EACCES (macOS Library)
- Updated dependencies []:
- oxlint-plugin-react-doctor@0.2.1
0.2.0
Minor Changes
- `5be2ead` - Add configuration-level controls for React Doctor's rule output. Users can now set top-level
rulesandcategoriesseverity overrides, tune individual output surfaces (cli,prComment,score, andciFailure) by tag/category/rule id, and rely on registered rule-family tags such asdesign,react-native,server-action,test-noise, andmigration-hintfor broad filtering.
The scan pipeline now applies those controls both when generating the oxlint config and when post-processing diagnostics, so "off" can skip rules before they run while "warn" / "error" restamp emitted diagnostics consistently across the CLI, score, PR comments, and CI failure gate. The oxlint plugin also exposes shared rule-set maps that the ESLint plugin reuses for its flat configs.
Expose the GitHub Action's annotations input so workflow users can opt into inline PR annotations without dropping down to the raw CLI.
- `809e38c` - Extract project / dependency / framework detection, the oxlint runner +
scoring engine, and the shared TypeScript type layer out of the react-doctor monolith into three new public workspace packages: @react-doctor/types, @react-doctor/project-info, and @react-doctor/core (#249). The oxlint plugin is restructured into per-rule modules under src/plugin/rules/<category>/<rule>.ts with a codegen'd rule-registry.ts (#218, #228, #230, #231, #234, #235, #236, #242). Land the user-feedback sweep (#208): scoring transparency hooks, per-rule severity + rule-set selection config options, and reduced false positives across the design / Tailwind / state-and-effects rule families. Reorganise the CLI into cli/commands/ + cli/utils/ (#250), and forward reactMajorVersion through programmatic diagnose() (#174).
Patch Changes
- `29b7229` - Add
oxlint-plugin-react-doctortodependenciesso it is installed
alongside the CLI. The bundler correctly externalises the plugin (oxlint loads it by file path at runtime) but it was missing from the published dependency list, causing ERR_MODULE_NOT_FOUND on npx react-doctor.
- `99f6a6a` - Rule-fix wave for the 0.2.0-beta.5 release:
- Scope
no-secrets-in-client-codeto client-reachable bindings -
skips server-only modules, public env-prefixed values, and locally-classified safe files (#252).
nextjs-no-side-effect-in-get-handlerstops flagging
response.headers.set(...) and locally-constructed Map / Set / Headers inside GET handlers; the same safe-bindings classifier benefits server-auth-actions and the TanStack Start get-mutation rule (#260).
async-defer-awaitno longer reports awaits inside destructured
patterns with defaults, bare-statement early-returns, or awaits guarded by an earlier if … return … (#265).
js-length-check-firstdetects length guards anywhere earlier in
an && chain, not only as the immediate left operand (#269).
async-parallelis suppressed in test files, browser-fixture /
Playwright helpers, and ordered UI flows where serial awaits are deliberate (#270).
js-combine-iterationsskips lazyIteratorhelper chains
(Iterator.from, Iterator.prototype.{map,filter,take,drop,…}) whose evaluation semantics differ from Array.prototype (#272, resolves #205).
no-prevent-defaultis framework-aware: Remix / Next.js
progressive-enhancement form handlers, synthetic event types with no documented alternative, and form onSubmit handlers that subsequently call fetch / a server action no longer trip (#274).
- New per-surface diagnostic controls in
@react-doctor/core+
react-doctor: design and Tailwind cleanup categories are demoted from the default PR-comment surface while staying visible in the CLI report and at the CI failure gate (#271).
- `10d5de8` - Fix workspace packages not being bundled into dist, causing
ERR_MODULE_NOT_FOUND: Cannot find package '@react-doctor/core' when running the published CLI.
- Replace the hand-rolled glob-to-regex compiler with `picomatch`, the proven matcher behind
chokidar,fast-glob, andmicromatch. The previous compiler turned patterns like**/**/**/**/**/foo.tsxinto nested optional(?:.+/)?groups whose backtracking is exponential in the number of**segments - a 20-deep pattern hung for over 30 seconds on a 60-character non-matching input.- Reject obviously pathological patterns early with a clear
InvalidGlobPatternErrorcarrying the offending pattern and a human-readable reason, instead of crashing the scan. Limits live in@react-doctor/core/constants(MAX_GLOB_PATTERN_LENGTH_CHARS = 1024,MAX_GLOB_PATTERN_WILDCARD_COUNT = 24) and bound worst-case work regardless of the underlying engine. Real-world ignore patterns like**/foo/**/bar/**/*.tsxsit well under the cap. - Surface invalid
ignore.filesandignore.overrides[*].filesentries as[react-doctor] …warnings on stderr and skip just the bad pattern, so a single typo no longer takes the whole scan down. - Add regression tests covering the worst-case patterns (deeply-stacked globstars and dense
a*a*a*…alternations) and the validation surface.
- Reject obviously pathological patterns early with a clear
rn-* rule fired on every file in a project whose top-level framework was detected as React Native or Expo - even on sibling workspaces that were clearly web targets. In a mixed RN + web monorepo (apps/mobile alongside apps/web and packages/storybook) the rules would noisily report issues against Next.js, Vite, Docusaurus, Storybook, and plain React DOM packages where they don't apply.
React Native rules now walk up to the file's nearest package.json before running. The rule body is skipped when the package declares a web-only framework (next, vite, react-scripts, gatsby, @remix-run/react, @docusaurus/core, @storybook/*, or plain react-dom without an RN sibling) and stays active when the package declares react-native, expo, react-native-tvos, react-native-windows, react-native-macos, anything under the @react-native/ or @react-native- community namespaces (@react-native-firebase/*, @react-native-async-storage/*, @react-native-community/*, …), or Metro's top-level "react-native" resolution field.
The detection is bidirectional: a web-rooted monorepo (root package.json declares next or vite) still loads rn-* rules when any workspace targets React Native or Expo, so the rules now fire on apps/mobile of a next-rooted repo as well as the inverse layout that the file-level boundary alone covered.
rn-no-raw-text additionally skips raw text inside Platform.OS === "web" branches: if, ?:, and && / || short-circuits, the mirror Platform.OS !== "web" else branches, switch (Platform.OS) { case "web": … } case bodies, and the web arm of Platform.select({ web: …, default: … }). Optional chaining (Platform?.OS) and the TS non-null assertion (Platform.OS!) parse the same way as the bare form. The walker stops at function and Program boundaries so JSX defined inside a callback hoisted out of a Platform.OS branch does not inherit the parent guard.
Native-only file extensions (.ios.tsx, .android.tsx, .native.tsx) keep the rule active even when the surrounding package classification is ambiguous.
- `99f6a6a` - False-positive sweep across the rule plugin and the oxlint runner:
- Gate React-19-only rules on the detected React major version so they
stay silent on React 18 projects, with hardened catalog / peer-range / workspace traversal in @react-doctor/project-info (#254).
- Treat early-return guards as render-reachable state reads so
rerender-state-only-in-handlers / no-event-trigger-state stop recommending useRef for state that gates render output (#255).
- Narrow
no-effect-event-handler- DOM imperatives, prop callbacks
invoked from effects, and side effects routed through a stable ref are no longer reclassified as handler-only (#256).
- Suppress rules-of-hooks diagnostics on locally-defined
useX
helpers that are not React hooks, and add the no-em-dash-in-jsx-text / no-three-period-ellipsis typography rules (#257).
- Collapse duplicate oxlint diagnostics and recover diagnostics from
large monorepo projects via batched runs + a new dedupe-diagnostics helper in @react-doctor/core (#262).
including pnpm and Bun catalog references) and gate Tailwind-aware rules on it. design-no-redundant-size-axes (which suggests collapsing w-N h-N → size-N) now stays silent on Tailwind v3.0 … v3.3 - those versions predate the size-N shorthand and the suggestion would generate classes that don't compile. The rule still fires on Tailwind v3.4+, v4+, and when the Tailwind version cannot be resolved.
A new tailwindVersion field is added to ProjectInfo and printed during scans so it's visible alongside the detected React version and framework.
- Updated dependencies [`99f6a6a`, `529015d`, `5be2ead`, `99f6a6a`, `809e38c`]:
- oxlint-plugin-react-doctor@0.2.0
0.2.0-beta.6
Minor Changes
- Add configuration-level controls for React Doctor's rule output. Users can now set top-level
rulesandcategoriesseverity overrides, tune individual output surfaces (cli,prComment,score, andciFailure) by tag/category/rule id, and rely on registered rule-family tags such asdesign,react-native,server-action,test-noise, andmigration-hintfor broad filtering.
The scan pipeline now applies those controls both when generating the oxlint config and when post-processing diagnostics, so "off" can skip rules before they run while "warn" / "error" restamp emitted diagnostics consistently across the CLI, score, PR comments, and CI failure gate. The oxlint plugin also exposes shared rule-set maps that the ESLint plugin reuses for its flat configs.
Expose the GitHub Action's annotations input so workflow users can opt into inline PR annotations without dropping down to the raw CLI.
Patch Changes
- Replace the hand-rolled glob-to-regex compiler with `picomatch`, the proven matcher behind
chokidar,fast-glob, andmicromatch. The previous compiler turned patterns like**/**/**/**/**/foo.tsxinto nested optional(?:.+/)?groups whose backtracking is exponential in the number of**segments - a 20-deep pattern hung for over 30 seconds on a 60-character non-matching input.- Reject obviously pathological patterns early with a clear
InvalidGlobPatternErrorcarrying the offending pattern and a human-readable reason, instead of crashing the scan. Limits live in@react-doctor/core/constants(MAX_GLOB_PATTERN_LENGTH_CHARS = 1024,MAX_GLOB_PATTERN_WILDCARD_COUNT = 24) and bound worst-case work regardless of the underlying engine. Real-world ignore patterns like**/foo/**/bar/**/*.tsxsit well under the cap. - Surface invalid
ignore.filesandignore.overrides[*].filesentries as[react-doctor] …warnings on stderr and skip just the bad pattern, so a single typo no longer takes the whole scan down. - Add regression tests covering the worst-case patterns (deeply-stacked globstars and dense
a*a*a*…alternations) and the validation surface.
- Reject obviously pathological patterns early with a clear
- Updated dependencies []:
- oxlint-plugin-react-doctor@0.2.0-beta.6
0.2.0-beta.5
Patch Changes
cli/utils/inspect-flags.ts flag + companion cli/utils/resolve-cli-inspect-options.ts and cli/utils/validate-mode-flags.ts plumbing wire the new @react-doctor/core surface filter into cli/commands/inspect.ts and inspect.ts. Design + Tailwind cleanup categories are demoted from the default PR-comment surface so they no longer dominate code review output, while still appearing in the CLI report and at the CI failure gate. Documented in the package README and the new action.yml knobs.
- Inherits the rule-fix wave from
oxlint-plugin-react-doctor@0.2.0-beta.5 (rules are bundled into the CLI): no-secrets-in-client-code scoping (#252), nextjs-no-side-effect-in-get-handler safe local bindings (#260), async-defer-await false-positive fixes (#265), js-length-check-first &&-chain detection (#269), async-parallel test / browser-fixture suppression (#270), js-combine-iterations lazy Iterator skip (#272), and no-prevent-default framework awareness (#274).
rn-* rule fired on every file in a project whose top-level framework was detected as React Native or Expo - even on sibling workspaces that were clearly web targets. In a mixed RN + web monorepo (apps/mobile alongside apps/web and packages/storybook) the rules would noisily report issues against Next.js, Vite, Docusaurus, Storybook, and plain React DOM packages where they don't apply.
React Native rules now walk up to the file's nearest package.json before running. The rule body is skipped when the package declares a web-only framework (next, vite, react-scripts, gatsby, @remix-run/react, @docusaurus/core, @storybook/*, or plain react-dom without an RN sibling) and stays active when the package declares react-native, expo, react-native-tvos, react-native-windows, react-native-macos, anything under the @react-native/ or @react-native- community namespaces (@react-native-firebase/*, @react-native-async-storage/*, @react-native-community/*, …), or Metro's top-level "react-native" resolution field.
The detection is bidirectional: a web-rooted monorepo (root package.json declares next or vite) still loads rn-* rules when any workspace targets React Native or Expo, so the rules now fire on apps/mobile of a next-rooted repo as well as the inverse layout that the file-level boundary alone covered.
rn-no-raw-text additionally skips raw text inside Platform.OS === "web" branches: if, ?:, and && / || short-circuits, the mirror Platform.OS !== "web" else branches, switch (Platform.OS) { case "web": … } case bodies, and the web arm of Platform.select({ web: …, default: … }). Optional chaining (Platform?.OS) and the TS non-null assertion (Platform.OS!) parse the same way as the bare form. The walker stops at function and Program boundaries so JSX defined inside a callback hoisted out of a Platform.OS branch does not inherit the parent guard.
Native-only file extensions (.ios.tsx, .android.tsx, .native.tsx) keep the rule active even when the surrounding package classification is ambiguous.
- Updated dependencies [`529015d`]:
- oxlint-plugin-react-doctor@0.2.0-beta.5
0.2.0-beta.4
Patch Changes
- Add
oxlint-plugin-react-doctortodependenciesso it is installed
alongside the CLI. The bundler correctly externalises the plugin (oxlint loads it by file path at runtime) but it was missing from the published dependency list, causing ERR_MODULE_NOT_FOUND on npx react-doctor.
- Updated dependencies []:
- oxlint-plugin-react-doctor@0.2.0-beta.4
0.2.0-beta.3
Patch Changes
- `10d5de8` - Fix workspace packages
(@react-doctor/core, @react-doctor/project-info, @react-doctor/types) not being bundled into the published dist/ output, which caused ERR_MODULE_NOT_FOUND: Cannot find package '@react-doctor/core' on npx react-doctor after the package extraction in beta.2. Vite config now treats the workspace dependencies as bundle-time inputs.
- Inherits the
#253 no-barrel-import index-resolution fix from oxlint-plugin-react-doctor@0.2.0-beta.3 (rules are bundled into the CLI).
- Updated dependencies []:
- oxlint-plugin-react-doctor@0.2.0-beta.3
0.2.0-beta.2
Minor Changes
detection, the oxlint runner, scoring, or the shared type layer inline - those modules now live in @react-doctor/types, @react-doctor/project-info, and @react-doctor/core and are consumed as workspace dependencies. Bundled into dist/ on publish (see also #253 bundler follow-up in beta.3). The react-doctor, react-doctor inspect, and react-doctor install binaries are surface-compatible with 0.1.6.
utils/, mirroring the layout ported from react-grab. Each subcommand has a dedicated module (inspect, install, version / help). No user-visible change to flags or output.
Patch Changes
Tailwind / state-and-effects rule groups, surface per-rule scoring contributions in react-doctor inspect, and add --severity / --rule-set CLI options plus their react-doctor.config.json counterparts. Closes the bulk of the feedback collected on 0.1.x.
- #174 - Forward
reactMajorVersion through the programmatic diagnose() entry point so embedders running react-doctor inside their own pipeline (Vercel AI Code Review sandbox and friends) get the same React-19 rule gating the CLI gets.
including pnpm and Bun catalog references) and gate Tailwind-aware rules on it. design-no-redundant-size-axes (which suggests collapsing w-N h-N → size-N) now stays silent on Tailwind v3.0 … v3.3 - those versions predate the size-N shorthand and the suggestion would generate classes that don't compile. The rule still fires on Tailwind v3.4+, v4+, and when the Tailwind version cannot be resolved.
A new tailwindVersion field is added to ProjectInfo and printed during scans so it's visible alongside the detected React version and framework.
0.1.6
Patch Changes
- `e9e4217` - Harden
discover-projectandresolve-diagnose-target: tighter
workspace-root detection constants, additional regression coverage in tests/diagnose.test.ts and tests/discover-project.test.ts for the nested-subproject fallback added in 0.1.5.
0.1.5
Patch Changes
requested directory has no root package.json, instead of crashing with No package.json found in <directory>. This unblocks external review runners (e.g. the Vercel AI Code Review sandbox) that point diagnose() at the cloned repo root for projects whose package.json lives in a subfolder like apps/web. When neither the root nor any nested subdirectory contains a React project, diagnose() now throws a clearer No React project found in <directory> error.
- #200 - Typed
errors from diagnose() plus a rootDir config option so embedders can target a specific subdirectory without relying on cwd inference.
- #201 - Integrate
eslint-plugin-react-you-might-not-need-an-effect into the curated rule set so its useEffect-elimination diagnostics flow into the score alongside react-doctor's own state-and-effects rules.
- #194 - Resolve
the React version from Bun grouped catalogs (in addition to pnpm catalogs) so monorepos using Bun for dependency hoisting still get an accurate React major back from the catalog resolver.
- #196 - Match
react-doctor-disable* suppression comments that carry descriptive trailing text (e.g. // react-doctor-disable-next-line rule -- why) instead of requiring a bare comment. Resolves #159.
- #198 - Expose
--why as a documented public alias for --explain in the CLI. Resolves #161.
- #195 - The
GitHub Action's score step is output-only and never fails the job, so consumers can gate on the score themselves without losing the run. Resolves #190.
- #197 - Docs:
clarify that ignore.overrides covers per-file rule ignores.
- #199 - Docs:
full GitHub Actions workflow example and inputs reference.
0.1.4
Patch Changes
- `a63d5d5` - CLI scan output reformat. Adds
utils/wrap-indented-text.tsfor
consistent wrapping of multi-line diagnostic recommendations, expands the scan-summary types to carry per-line wrap state, and threads the helper through scan.ts. Backed by the new wrap-indented-text.test.ts unit suite and a cli-and-output regression suite that snapshots the rendered CLI output.
0.1.3
Patch Changes
- #184 - Add a
rawTextWrapperComponents config option so projects can teach rn-no-raw-text about their own <Text> wrappers (e.g. design-system primitives that render Text internally). Resolves #183.
- #182 - Restore
React Compiler rules to error severity. They had silently regressed to warn in 0.1.0 when the plugin-resolution gating landed, masking Compiler-blocking violations behind the warning lane.
- #181 - Website
fix: keep the diagnostic count next to the rule name on narrow widths in the leaderboard / diagnostic listings.
- `cca5808` - Promote
react-hooks-js/*diagnostics to errors so projects with
React Hooks rule violations no longer pass with a clean score.
- `9ee3a6d` - Refresh the website's terminal demo to match the new CLI output
format introduced in 0.1.1 / 0.1.4.
0.1.2
Patch Changes
- `6ddb02c` - Polish follow-up to the 0.1.1 CLI redesign. Consolidates duplicated
scan-summary literals into constants.ts, simplifies scan.ts to drop a redundant branch (-9 LOC), and tightens the spinner.ts helper so its cleanup is symmetric with start. No user-visible behaviour change.
0.1.1
Patch Changes
- #178 - CLI
scan-summary redesign. The final report now inlines a category breakdown (state-and-effects / design / bundle-size / …) and a compact rule list grouped under each category, replacing the previous single-line counts. Verbose mode keeps the per-diagnostic listing.
0.1.0
Minor Changes
- d71a6bf: feat(react-doctor): ship rules as an ESLint plugin (
react-doctor/eslint-plugin)
The same React Doctor rule set that powers the CLI scan and the react-doctor/oxlint-plugin export is now available as a first-class ESLint plugin. Drop it into your eslint.config.js flat config and diagnostics surface inline through whichever IDE / agent / pre-commit hook already speaks ESLint - no separate react-doctor invocation needed.
```js // eslint.config.js import reactDoctor from "react-doctor/eslint-plugin";
export default [ reactDoctor.configs.recommended, reactDoctor.configs.next, // composable framework presets reactDoctor.configs["react-native"], reactDoctor.configs["tanstack-start"], reactDoctor.configs["tanstack-query"], // reactDoctor.configs.all, // every rule at react-doctor's default severity ]; ```
The exported recommended, next, react-native, tanstack-start, tanstack-query, and all configs reuse the exact severity maps the react-doctor CLI emits to oxlint, so behavior stays in lock-step between engines. You can also cherry-pick individual rules under the react-doctor/* namespace.
The visitor signatures inside each rule are already ESLint-compatible (create(context) => visitors); the new export wraps each rule with the ESLint-required meta (type, docs.url, schema) and exposes the plugin shape ESLint v9 flat configs expect. Closes #143.
- d71a6bf: feat(react-doctor): adopt the project's existing oxlint / eslint config and factor those rules into the score
When a project has a JSON-format oxlint or eslint config (.oxlintrc.json or .eslintrc.json) at the scanned directory or any ancestor up to the nearest project boundary (.git directory or monorepo root), react-doctor now folds that config into the same scan via oxlint's extends field. The user's existing rules fire alongside the curated react-doctor rule set, and the resulting diagnostics count toward the 0–100 health score - no separate oxlint / eslint invocation needed.
Behavior change on upgrade. Projects with an existing .oxlintrc.json / .eslintrc.json will see new diagnostics flow into the score on first run; the score may drop. Set "adoptExistingLintConfig": false in react-doctor.config.json (or the "reactDoctor" key in package.json) to preserve the previous behavior. customRulesOnly: true also implies opt-out, since that mode runs only the react-doctor/* plugin.
Resilience. If oxlint can't load the user's config (broken JSON, missing plugin, unknown rule name), react-doctor logs the reason on stderr and retries the scan once without extends so the score is still computed off the curated rule set instead of failing the whole lint pass.
Coverage broadened. Diagnostics on .ts and .js files are now reported (previously the parser dropped everything that wasn't .tsx / .jsx). This affects react-doctor's own JS-performance / bundle-size rules in addition to adopted user rules.
Limitations. Only JSON configs are picked up: oxlint's extends cannot evaluate JS or TS, so flat configs (eslint.config.js), .eslintrc.{js,cjs}, and oxlint.config.ts are silently skipped. Rule-level severities ("rules": {...}) flow through, but category-level enables ("categories": {...}) do not - react-doctor's local categories block always wins. Closes #143.
- d71a6bf: feat(react-doctor): add 11 new lint rules - 3 state / correctness, 8 design system
3 new state / correctness rules (all warn):
react-doctor/no-direct-state-mutation- flagsstate.foo = xand
in-place array mutators (push / pop / shift / unshift / splice / sort / reverse / fill / copyWithin) on useState values. Tracks shadowed names through nested function params and locals so a handler that re-binds the state name doesn't false-positive.
react-doctor/no-set-state-in-render- flags only unconditional
top-level setter calls so the canonical if (prev !== prop) setPrev(prop) derive-from-props pattern stays clean.
react-doctor/no-uncontrolled-input- catches<input value={…}>
without onChange / readOnly, value + defaultValue conflicts, and useState() flip-from-undefined. Bails on JSX spread props ({...register(…)}, Headless UI, Radix) where onChange may come from spread.
8 new design-system rules in `react-ui.ts` (all warn):
react-doctor/design-no-bold-heading-
font-bold / font-extrabold / font-black or inline fontWeight ≥ 700 on h1–h6.
react-doctor/design-no-redundant-padding-axes- collapse
px-N py-N → p-N.
react-doctor/design-no-redundant-size-axes- collapsew-N h-N→
size-N.
react-doctor/design-no-space-on-flex-children- usegap-*over
space-*-*.
react-doctor/design-no-em-dash-in-jsx-text- em dashes in JSX
text.
react-doctor/design-no-three-period-ellipsis-Loading...→
Loading….
react-doctor/design-no-default-tailwind-palette-
indigo-* / gray-* / slate-* reads as the Tailwind template default; reports every offending token in the className (not just the first).
react-doctor/design-no-vague-button-label-OK/Continue/
Submit etc.; recurses into <>…</> fragment children.
Each new rule has dedicated regression tests covering both the positive trigger and the false-positive cases above.
Other
- Hoists shared regex / token patterns into the appropriate
constants.ts per AGENTS.md.
- d71a6bf: remove(react-doctor): drop browser entrypoints, browser CLI, and the
react-doctor-browser workspace package
Removed package exports. react-doctor/browser and react-doctor/worker are no longer published. Imports of either subpath will fail with ERR_PACKAGE_PATH_NOT_EXPORTED. If you depended on the in-browser diagnostics pipeline (caller-supplied projectFiles map + runOxlint callback running oxlint in a Web Worker), pin react-doctor@0.0.47 or vendor the relevant modules from the archive/browser git branch.
Removed CLI subcommand. react-doctor browser … (start, stop, status, snapshot, screenshot, playwright) is gone. The long-running headless Chrome session, ARIA snapshot helpers, screenshot capture, and --eval Playwright harness are no longer available from the CLI.
Removed companion package. The react-doctor-browser npm package (headless browser automation, CDP discovery, system Chrome launcher, cross-browser cookie extraction) has been removed from the workspace. The last published version remains installable on npm but will not receive further updates.
Why. The browser surface area was unused inside the monorepo (the website does not import it) and added a heavy dependency footprint (playwright, libsql, etc.) for a public API with no known internal consumers. Removing it tightens what react-doctor is responsible for: the diagnostics CLI, the Node react-doctor/api, and the react-doctor/eslint-plugin / react-doctor/oxlint-plugin exports.
The full removed source remains available on the archive/browser branch for anyone who wants to fork or vendor the modules.
Patch Changes
- 2aebfa6: fix(react-doctor): support block comment forms of
react-doctor-disable-line/react-doctor-disable-next-line
The inline-suppression matcher previously only recognized line comments (// react-doctor-disable-…). Block comments - including the JSX form {/* react-doctor-disable-next-line … */}, which is the only suppression form legal directly inside JSX - were silently ignored, forcing users to write {/* // react-doctor-disable-line … */} as a workaround. Both forms now work, and either accepts a comma- or whitespace-separated rule list or no rule id (suppress every diagnostic on the targeted line). Closes #144.
- 2aebfa6: fix(react-doctor): stop flagging
useStateasuseRefwhen state reaches render throughuseMemo, derived values, or contextvalue
rerender-state-only-in-handlers (the rule that suggests "use useRef because this state is never read in render") only checked whether the state name appeared by name in the component's return JSX. That heuristic produced loud false positives for ordinary patterns:
- state filtered/derived through
useMemo→ JSX uses the memo result- state passed as the
valueof a React Context Provider - state combined with other variables into a rendered constant
- state passed as the
Following the bad hint and converting these to useRef silently broke apps because ref.current = … does not trigger a re-render - search results stopped updating, dialogs stayed open, and context consumers saw stale snapshots.
The rule now performs a transitive "render-reachable" analysis on top-level component bindings. A useState is only flagged when neither the value itself nor anything derived from it (recursively) appears anywhere in the rendered JSX, including attribute values like <Context value={…}>, style={…}, className={…}, etc. Truly transient state (e.g. a scroll position only stored to be ignored) still fires. Closes #146.
and 11 new lint rules: 3 state / correctness rules (no-direct-state-mutation, no-set-state-in-render, no-uncontrolled-input) and 8 design-system rules (see the dedicated bullet above).
forwardRef-deprecation / new context API / new ref-callback cleanup / use() adoption migration paths.
subscriptions that should be useSyncExternalStore (concurrent-mode safe, tearing-resistant).
written from an event handler and only read from the rendered JSX return - a frequent prop-derivation antipattern.
effect's setState triggers another effect, which is almost always a signal to collapse the chain into a derived value or event handler.
(no-mutable-in-deps, no-mirror-prop-effect, effect-needs-cleanup) plus shared dependency-tracking infrastructure.
doesn't fire on React 18 projects (where useEffectEvent is not available).
lookups so callbacks hoisted out of effects don't inherit the surrounding effect classification.
overrides, and near-miss hints for misspelled rule ids.
validation, --explain working inside monorepos, JSX generics parsing, line-comment skip semantics, and a single-pass evaluator for the suppression matcher.
default scan output stays scannable on large projects; --verbose restores the full listing.
rules-of-hooks / handler-detection / render-reachable code paths.
entry point so embedders get the same React-19 rule gating the CLI uses.
the adopt-config noise introduced in d71a6bf when the user's config contains unknown rules.
0.0.47
Patch Changes
- 6a0e6d6: chore(react-doctor): bump oxlint to ^1.62.0
Pulls in oxlint v1.61.0 + v1.62.0 improvements (additional Vue rules, jest/vitest rule splits, autofix for prefer-template, no-unknown-property support for React 19's precedence prop, jsx-a11y/anchor-is-valid attribute settings, and various correctness fixes). The release-line breaking changes are internal Rust API only - oxlint's CLI and config schema are unchanged.
- dbf200d: fix(react-doctor): filter React Compiler rules to those the loaded
eslint-plugin-react-hooksactually exports
Follow-up to the #141 fix in 0.0.46. The peer range ^6 || ^7 allows v6.x of eslint-plugin-react-hooks, which doesn't expose the void-use-memo rule (added in v7). When a v6 user had React Compiler detected, oxlint failed with Rule 'void-use-memo' not found in plugin 'react-hooks-js'. The config now introspects the loaded plugin's rules map and only enables react-hooks-js/* entries that the installed version actually exports - so future rule additions or removals can no longer crash a scan.
0.0.46
Patch Changes
- c13a8df: fix(react-doctor): skip React Compiler rules when
eslint-plugin-react-hooksisn't installed
When a project had React Compiler detected but the optional peer eslint-plugin-react-hooks was not installed, oxlint failed with react-hooks-js not found because the React Compiler rules were emitted into the config without the corresponding plugin entry. Gate REACT_COMPILER_RULES on successful plugin resolution so a missing optional peer silently skips them instead of crashing the scan (#141).
0.0.45
Patch Changes
- 6b07924:
react-doctor installnow delegates skill installation to
`agent-install` 0.0.4, which natively models 54 supported coding agents (up from the 8 we previously hand-rolled).
Behavior changes:
- Detection is now the union of CLI binaries on
$PATH(the previous
signal) and config dirs in $HOME (~/.claude, ~/.cursor, ~/.codex, ~/.factory, ~/.pi, etc.). This catches agents the user has run at least once even if the CLI is no longer on $PATH, and vice versa.
- All 8 originally documented agents stay supported: Claude Code,
Codex, Cursor, Factory Droid, Gemini CLI, GitHub Copilot, OpenCode, Pi.
- 46 newly supported agents via upstream
agent-install@0.0.4:
Goose, Windsurf, Roo Code, Cline, Kilo Code, Warp, Replit, OpenHands, Qwen Code, Continue, Aider Desk, Augment, Cortex, Devin, Junie, Kiro CLI, Crush, Mux, Pochi, Qoder, Trae, Zencoder, and many more.
- Bug fix: malformed
SKILL.mdfrontmatter now surfaces as an error
instead of a silent "installed for ..." success with zero files written. Build-time validation in vite.config.ts also catches this before publish.
0.0.44
Patch Changes
- `57467cd` - Patch follow-up to the 0.0.43 ignore-respecting refactor: misc
rough edges in the new ignore-pattern collector and inline-disable matcher.
0.0.43
Patch Changes
- Respect existing eslint / oxlint / prettier ignores by default. React Doctor now honors
.gitignore,.eslintignore,.oxlintignore,.prettierignore, and.gitattributeslinguist-vendored/linguist-generatedannotations, plus inline// eslint-disable*and// oxlint-disable*comments. Previously inline disable comments were neutralized so react-doctor saw through every prior suppression - this surprised users who hadeslint-disablein place for legitimate reasons. Behavior change: existing users may see fewer findings (previously-suppressed code is now correctly suppressed). To restore the old "audit everything" behavior, set"respectInlineDisables": falseinreact-doctor.config.jsonor pass--no-respect-inline-disableson the CLI. - Internals: the ignore-pattern collector now writes a single combined
--ignore-pathfile rather than passing N--ignore-patternargs; this removes abaseArgs-length pressure point that could shrink batch sizes on large diffs. Boolean config fields (lint,deadCode,verbose,customRulesOnly,share,respectInlineDisables) are now coerced from the common"true"/"false"JSON-string typo at config-load time, with a warning. TheparseOxlintOutput"no files to lint" workaround is now locale-agnostic (it skips any noise before the first{). The non-git audit-mode fallback walks the project tree directly instead of silently no-op'ing whengit grepisn't available. New regression suite covers all of the above end-to-end.
0.0.42
Patch Changes
- 79fb877: Fix
Dead code detection failed (non-fatal, skipping)(#135). The plugin-failure detector now walks the error cause chain, matches Windows-style paths, plugin configs without a leading directory, and parser errors, so knip plugin loading errors are recovered from in more environments. The retry loop also now surfaces the original knip error after exhausting attempts (previously could throw a genericUnreachableerror) and only disables knip plugin keys it actually recognizes. Dead-code and lint failures are now reported with the full cause chain instead of a single wrappedError loading …line. - 391b751: Fix knip step ignoring workspace-local config in monorepos (#136). When a workspace owns its own knip config (
knip.json,knip.jsonc,knip.ts, etc.),runKnipnow runs knip withcwd = workspaceDirectoryso the config is discovered, instead of running from the monorepo root with--workspaceand silently falling back to knip's defaults - which mass-flagged every file asUnused filefor setups like TanStack Start whose entry layout doesn't match the defaults. Behavior for monorepos with a root-levelknip.jsoncontaining aworkspacesmapping is unchanged.
0.0.41
Patch Changes
- `1fdc9a0` - Patch follow-up to the 0.0.39 browser-entrypoint work: misc
bundling fixes for the now-removed react-doctor/browser and react-doctor/worker subpath exports.
0.0.40
Patch Changes
- `874f7bc` - Publishing-pipeline retry of 0.0.39 (no code delta).
0.0.39
Patch Changes
detection. Knip 6.x returns issues.files as an IssueRecords object instead of a Set<string>. The dead-code pass now handles both shapes (and arrays) defensively.
processBrowserDiagnostics API so the website's in-browser demo can run the same scoring + rule pipeline as the CLI without shelling out. Shared diagnose helpers and the browser scorer are extracted so bundles can omit the proxy-fetch path. (The browser surface was later removed in 0.1.0 - see that section.)
- `b5519b6` - Inject the browser scorer at build time so the bundled
react-doctor/browser output omits the proxy-fetch / Node-only score path.
0.0.38
Patch Changes
- `8b0485a` - GitHub Action improvements (input validation + step output cleanups),
clickable file paths in CLI diagnostic output (terminal hyperlinks via OSC-8), and a website hydration fix.
- `bb5188f` - Document every config option supported by
react-doctor.config.json
in the README.
- `100731c` -
install-skillanddetect-agentsformatting fixes so CI's
format:check step stays green.
0.0.37
Patch Changes
- `f1bd776` - Republish 0.0.36 after a botched skill payload - the
install-skill SKILL.md frontmatter was malformed and the prior publish shipped an unusable skill. No code delta vs 0.0.36 beyond the SKILL.md regeneration.
0.0.36
Patch Changes
rule family - bold headings, redundant padding/size axes, vague button labels, etc.). Expanded substantially in 0.1.0.
- `074f854` - Add the
react-doctor installsubcommand that installs the
React Doctor SKILL.md into the user's configured coding agents.
0.0.35
Patch Changes
- `7136aa5` - Republish 0.0.34 after a packaging hiccup; no code delta.
0.0.34
Patch Changes
hygiene, useServerFn adoption hints).
detection.
0.0.33
Patch Changes
- `87d4b86` - Republish 0.0.32 after a packaging hiccup; no code delta.
0.0.32
Patch Changes
rule false-positive fixes, CLI option polish, and detection hardening.
/ member-expression accessors no longer count as setter invocations.
0.0.31
Patch Changes
semantics, CLI ergonomics, React Native detection, Next.js detection, the --offline flag, and monorepo discovery.
before linting starts (previously they were linted then filtered).
0.0.30
Patch Changes
- `c405f4a` - Resolve multiple GitHub issues (#71, #72, #76, #77, #83, #84, #86,
#87, #89, #92, #93, #94): broad rule false-positive sweep across detection, scoring, and rule output formatting.
- `97b21f1` - Replace
fs.existsSyncwith the sharedisFileutility for
consistent file checks across the codebase.
0.0.29
Patch Changes
changed in the PR, plus optional PR-comment posting from the action step.
family when a project targets them.
0.0.28
Patch Changes
- `bd949cc` - Bump the Node version requirement, enhance the linting process with
improved error handling, and tighten the CI matrix.
0.0.27
Patch Changes
- `370ea4c` - Refactor CLI option handling and improve automated-environment
detection (CI / agent contexts).
- `051a02c` - Score-calculation refactor: extract shared constants and add the
proxy-fetch fallback used by the in-browser scorer.
- `bf21a87` - Fix
--fixdeeplink rendering issues introduced when the install
prompt was integrated into the CLI workflow.
0.0.26
Patch Changes
- `7716d6c` - Integrate the SKILL.md installation prompt directly into the CLI
workflow so first-run users get the skill installed without a separate command.
0.0.25
Patch Changes
longer auto-installs itself globally on first run. Resolves #43.
- `7e20da1` - Refactor the diagnostic payload structure used by the
score-estimation API and tighten its validation.
- `da83168` - Enhance the React Doctor skill installation script with detailed
usage instructions and support for multiple platforms.
0.0.24
Patch Changes
0.0.23
Patch Changes
0.0.22
Patch Changes
- `84bb6d5` / `61406e0` / `1b07fa2` / `0299fc4` - Patch sweep - rule false-positive fixes and formatting follow-ups.
0.0.21
Patch Changes
- `73da9e2` - Add the
--offlineflag so the CLI skips network calls (telemetry,
the leaderboard upload step) for users behind firewalls or in air-gapped CI.
0.0.20
Patch Changes
produce diagnosable error messages instead of a silent non-zero exit. Also updates CLI option docs.
0.0.19
Patch Changes
miscellaneous fixes.
0.0.18
Patch Changes
requirement; previously users without TypeScript installed got a warning on every scan.
0.0.17
Patch Changes
longer drops the rest of the scan output.
0.0.16
Patch Changes
- `06fb14e` - Improve error handling in the linting and dead-code analysis paths
so failures log a useful message instead of a stack trace.
- `595ca55` - Remove the video package from
main(kept on a separate branch for
asset generation).
0.0.15
Patch Changes
the README with usage docs for the published node API.
0.0.14
Patch Changes
- `90ffa0a` - Add
llms.txtso models discovering the package via the npm
registry can find structured docs.
- `e218d63` - Format the leaderboard data files to satisfy CI
format:check.
0.0.13
Patch Changes
refresh. Reverted in `28d820a` when the assets failed to render correctly on npm.
0.0.12
Patch Changes
- `b200689` / `4519747` / `7f0b4d2` / `6d1ae5e` / `5688c1f` / `6dd481a` / `ce8437e` - Iteration sweep on detection / output formatting / README copy
during the pre-1.0 stabilization push.
0.0.11
Patch Changes
REACT_DOCTOR_* env vars through the score-estimation API call so CI overrides actually take effect.
0.0.10
Patch Changes
- `e5ef934` - "Almost ready" milestone - the rule pipeline + scoring + CLI surface
are end-to-end functional for the first time. No discrete commits between 0.0.9 and 0.0.10 beyond the version bump itself.
0.0.9
Patch Changes
(Ami-style copy block formatting).
CLI prompt.
score line).
darken them.
- `b5ea69b` - Add the GitHub Action for CI integration and fix monorepo scanning
inside CI environments.
- `578e75a` - Auto-install globally in the background when run via
npx. (Later
removed in 0.0.25 / #44 because it surprised users.)
- `bde1167` - Add the video package and Ami skills for marketing-asset
generation.
0.0.8
Patch Changes
- `19fa34b` - Resolve a merge conflict in
cli.tsintroduced in 0.0.7.
- `eef87e0` - Use a single deeplink for
--fixinstead of the two-step deeplink
- sleep dance.
rules table, promote install above options).
0.0.7
Patch Changes
- `2ae9b87` - Fix the
--fixdeeplink to open the project with the correct cwd
and autosend the prompt.
0.0.6
Patch Changes
- `f9157c7` - Add the
no-side-effect-in-get-handlerrule and export the oxlint
plugin as a standalone entrypoint.
update the README with consumer-friendly docs.
automated rule application.
- `330afc2` / `7aa7b3f` / `05d2f79` / `4dfaeab` - Add ASCII doctor face / box branding to the CLI score output and
the website terminal.
- `b1f1abc` - Add the CI workflow for e2e tests, lint, and format.
- `4b481c8` - Add the React Doctor skill and the install prompt on first run.
0.0.5
Patch Changes
- `ccf404a` - Gracefully handle failures in
oxlint, the reduced-motion check,
and summary-file writing so a single subsystem can't take down the scan.
- `f1407d7` - Gracefully handle knip failures on non-React config files.
- Project scoring (the 0–100 health score) lands in this release.
0.0.4
Patch Changes
- `2d9a69b` - Add actionable help text, animation rules, per-rule summary files,
and strengthen the framework / dependency detection.
- `327c076` - Move the website source into
packages/website.
0.0.3
Patch Changes
- `680e7c4` - Reduce default scan noisiness - tighter default rule severity
thresholds for the first user-facing prerelease.
0.0.2
Patch Changes
- `a8770b7` - Add CLI scaffolding with the initial oxlint integration: scan
command, oxlint runner, diagnostic-collection pipeline.
0.0.1
Patch Changes
- `f50426b` - Initial publish - empty package scaffold to claim the npm name.