[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-11692":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":9,"totalLinesOfCode":9,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":9,"subscribersCount":16,"size":16,"stars1d":17,"stars7d":18,"stars30d":19,"stars90d":16,"forks30d":16,"starsTrendScore":20,"compositeScore":21,"rankGlobal":9,"rankLanguage":9,"license":9,"archived":22,"fork":22,"defaultBranch":23,"hasWiki":22,"hasPages":22,"topics":24,"createdAt":9,"pushedAt":9,"updatedAt":30,"readmeContent":31,"aiSummary":32,"trendingCount":16,"starSnapshotCount":16,"syncStatus":33,"lastSyncTime":34,"discoverSource":35},11692,"react-doctor","millionco\u002Freact-doctor","millionco","Your agent writes bad React. This catches it",null,"https:\u002F\u002Fgithub.com\u002Fmillionco\u002Freact-doctor","TypeScript",12548,396,23,10,0,288,884,1915,864,42.8,false,"main",[25,26,27,28,29],"agents","code-review","doctor","react","skill","2026-06-12 02:02:33","\u003Cpicture>\n  \u003Csource media=\"(prefers-color-scheme: dark)\" srcset=\".\u002Fassets\u002Freact-doctor-readme-logo-dark.svg\">\n  \u003Csource media=\"(prefers-color-scheme: light)\" srcset=\".\u002Fassets\u002Freact-doctor-readme-logo-light.svg\">\n  \u003Cimg alt=\"React Doctor\" src=\".\u002Fassets\u002Freact-doctor-readme-logo-light.svg\" width=\"180\" height=\"40\">\n\u003C\u002Fpicture>\n\n[![version](https:\u002F\u002Fimg.shields.io\u002Fnpm\u002Fv\u002Freact-doctor?style=flat&colorA=000000&colorB=000000)](https:\u002F\u002Fnpmjs.com\u002Fpackage\u002Freact-doctor)\n[![downloads](https:\u002F\u002Fimg.shields.io\u002Fnpm\u002Fdt\u002Freact-doctor.svg?style=flat&colorA=000000&colorB=000000)](https:\u002F\u002Fnpmjs.com\u002Fpackage\u002Freact-doctor)\n\nYour agent writes bad React, this catches it.\n\nOne command scans your codebase and outputs a **0 to 100 health score** with actionable diagnostics.\n\nWorks with Next.js, Vite, and React Native.\n\n### [See it in action →](https:\u002F\u002Freact.doctor)\n\n## Install\n\nRun this at your project root:\n\n```bash\nnpx react-doctor@latest\n```\n\nYou'll get a score (75+ Great, 50 to 74 Needs work, under 50 Critical) and a list of issues across state & effects, performance, architecture, security, and accessibility. Rules toggle automatically based on your framework and React version.\n\n> **Migration note:** React Doctor used to bundle [knip](https:\u002F\u002Fknip.dev\u002F) for dead-code detection. That integration was removed in v0.2 — if you want dead-code analysis, run `npx knip` directly as part of your own pre-commit or CI pipeline.\n\nhttps:\u002F\u002Fgithub.com\u002Fuser-attachments\u002Fassets\u002F07cc88d9-9589-44c3-aa73-5d603cb1c570\n\n## Install for your coding agent\n\nTeach your coding agent React best practices so it stops writing the bad code in the first place.\n\n```bash\nnpx react-doctor@latest install\n```\n\nYou'll be prompted to pick which detected agents to install for. Pass `--yes` to skip prompts.\n\nWorks with Claude Code, Cursor, Codex, OpenCode, and 50+ other agents.\n\n## GitHub Actions\n\nA composite action ships with this repository. Drop it into `.github\u002Fworkflows\u002Freact-doctor.yml`:\n\n```yaml\nname: React Doctor\n\non:\n  pull_request:\n  push:\n    branches: [main]\n\npermissions:\n  contents: read\n  pull-requests: write # required to post PR comments\n\njobs:\n  react-doctor:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@v5\n        with:\n          fetch-depth: 0 # required for `diff`\n      - uses: millionco\u002Freact-doctor@main\n        with:\n          diff: main\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n```\n\nWhen `github-token` is set on `pull_request` events, findings are posted (and updated) as a sticky PR comment. The action also exposes a `score` output (0–100) you can use in subsequent steps.\n\n**Inputs:** `directory`, `verbose`, `project`, `diff`, `github-token`, `fail-on` (`error` \u002F `warning` \u002F `none`), `offline`, `annotations`, `node-version`. See [`action.yml`](https:\u002F\u002Fgithub.com\u002Fmillionco\u002Freact-doctor\u002Fblob\u002Fmain\u002Faction.yml) for full descriptions.\n\n#### PR feedback modes\n\nPick one or both; they're independent.\n\n- **Comments only** (default): set `github-token`.\n- **Annotations only**: set `annotations: true`.\n- **Both**: set `github-token` and `annotations: true`. Annotation lines are stripped from the comment body.\n\n```yaml\n- uses: millionco\u002Freact-doctor@main\n  with:\n    diff: main\n    github-token: ${{ secrets.GITHUB_TOKEN }}\n    annotations: true\n```\n\nPrefer not to add a marketplace action? The bare `npx` form works too:\n\n```yaml\n- run: npx react-doctor@latest --fail-on warning\n```\n\n## PR blocking and exit codes\n\nTwo independent gates can block a PR — pick one or both:\n\n- **`--fail-on \u003Clevel>`** exits non-zero on diagnostics: `error` (default, any error-severity rule fires), `warning` (any diagnostic fires), or `none` (never). Runs against the `ciFailure` surface, so the default `design`-tag exclusion still applies.\n- **Score floor** — a follow-up step that reads the action's `score` output and `exit 1`s when it's below your threshold.\n\nCombine `--fail-on` with `--diff \u003Cbase>` to scope the gate to the PR's changed files only — that's the built-in way to fail on **new** regressions without dragging in baseline backlog. There is no separate `--fail-on-new` flag.\n\n`--annotations` (bare `npx` only) and `github-token` (sticky PR comment) are visualization layers and never change the exit code.\n\n### Examples\n\n**Advisory mode** — never blocks, always comments:\n\n```yaml\n- uses: millionco\u002Freact-doctor@main\n  with:\n    github-token: ${{ secrets.GITHUB_TOKEN }}\n    fail-on: none\n```\n\n**Regression-only mode** — fail only on new diagnostics introduced by the PR:\n\n```yaml\n- uses: actions\u002Fcheckout@v5\n  with:\n    fetch-depth: 0 # required for `diff`\n- uses: millionco\u002Freact-doctor@main\n  with:\n    diff: main\n    fail-on: warning\n    github-token: ${{ secrets.GITHUB_TOKEN }}\n```\n\n**Strict threshold mode** — fail when the baseline score drops below a floor:\n\n```yaml\n- id: doctor\n  uses: millionco\u002Freact-doctor@main\n  with:\n    fail-on: error\n    github-token: ${{ secrets.GITHUB_TOKEN }}\n- env:\n    SCORE: ${{ steps.doctor.outputs.score }}\n    FLOOR: \"80\"\n  run: |\n    if [ -n \"$SCORE\" ] && [ \"$SCORE\" -lt \"$FLOOR\" ]; then\n      echo \"::error::React Doctor score $SCORE is below floor $FLOOR\"\n      exit 1\n    fi\n```\n\nPin a specific `react-doctor` version when using a score floor — new rule releases can lower the score even when your code hasn't changed (see [Scoring](#scoring)).\n\n## Configuration\n\nCreate a `react-doctor.config.json` in your project root:\n\n```json\n{\n  \"ignore\": {\n    \"rules\": [\"react\u002Fno-danger\", \"jsx-a11y\u002Fno-autofocus\"],\n    \"files\": [\"src\u002Fgenerated\u002F**\"],\n    \"overrides\": [\n      {\n        \"files\": [\"components\u002Fmodules\u002Fdiff\u002F**\"],\n        \"rules\": [\"react-doctor\u002Fno-array-index-as-key\", \"react-doctor\u002Fno-render-in-render\"]\n      },\n      {\n        \"files\": [\"components\u002Fsearch\u002FHighlightedSnippet.tsx\"],\n        \"rules\": [\"react\u002Fno-danger\"]\n      }\n    ]\n  }\n}\n```\n\nThree nested keys, three layers of granularity — pick the narrowest one that fits:\n\n- **`ignore.rules`** silences a rule across the whole codebase.\n- **`ignore.files`** silences **every** rule on the matched files (use sparingly — it loses coverage for unrelated rules).\n- **`ignore.overrides`** silences only the listed rules on the matched files, leaving every other rule active. This is what you want when a single file (or glob) legitimately needs an exemption from one or two rules but should still be scanned for everything else.\n\nYou can also use the `\"reactDoctor\"` key in `package.json`. CLI flags always override config values.\n\nReact Doctor respects `.gitignore`, `.eslintignore`, `.oxlintignore`, `.prettierignore`, and `linguist-vendored` \u002F `linguist-generated` annotations in `.gitattributes`. Inline `\u002F\u002F eslint-disable*` and `\u002F\u002F oxlint-disable*` comments are honored too.\n\nIf you have a JSON oxlint or eslint config (`.oxlintrc.json` or `.eslintrc.json`), its rules get merged into the scan automatically and count toward the score. Set `adoptExistingLintConfig: false` to opt out.\n\n#### Surface controls (CLI, PR comments, score, CI failure)\n\nDiagnostics flow through four independent surfaces — `cli`, `prComment`, `score`, and `ciFailure` — and each one can be tuned per tag, category, or rule id. By default the `design` tag (Tailwind shorthand cleanup like `w-5 h-5 → size-5`, pure-black backgrounds, gradient text, …) stays visible on the local CLI but is excluded from the PR comment, the score, and the `--fail-on` gate so style cleanup can't dilute meaningful React findings:\n\n```json\n{\n  \"surfaces\": {\n    \"prComment\": {\n      \"includeTags\": [\"design\"],\n      \"excludeCategories\": [\"Performance\"]\n    },\n    \"score\": { \"includeRules\": [\"react-doctor\u002Fdesign-no-redundant-size-axes\"] },\n    \"ciFailure\": { \"excludeTags\": [\"test-noise\"] }\n  }\n}\n```\n\nEach surface accepts `includeTags`, `excludeTags`, `includeCategories`, `excludeCategories`, `includeRules`, and `excludeRules`. Include wins over exclude when both match. Run the CLI with `--pr-comment` (the GitHub Action passes it automatically when `github-token` is set) to apply the `prComment` surface to the printed output destined for sticky PR comments.\n\n#### Rule severity (`rules`, `categories`)\n\nSame shape as ESLint \u002F oxlint. `rules` is ESLint's exact field; `categories` mirrors oxlint's, keyed by React Doctor display categories (`\"React Native\"`, `\"Server\"`, `\"Architecture\"`, …).\n\n```json\n{\n  \"rules\": { \"react-doctor\u002Fno-array-index-as-key\": \"error\" },\n  \"categories\": { \"React Native\": \"warn\" }\n}\n```\n\nPer-rule wins over per-category. `\"off\"` short-circuits before the rule runs; `\"warn\"` \u002F `\"error\"` re-stamps the diagnostic so every channel — CLI, PR comment, score, `--fail-on` — sees the chosen severity, including for external-plugin rules. Use `surfaces` instead when you only want to hide a rule from one channel; use `ignore.tags` to silence a whole tag-defined family (`\"design\"`, `\"test-noise\"`, `\"migration-hint\"`) that doesn't align with a single category.\n\n#### Optional companion plugins\n\nWhen the following ESLint plugins are installed in the scanned project (or hoisted in your monorepo), React Doctor folds their rules into the same scan. Both are listed as **optional peer dependencies** — install only what you want.\n\n| Plugin                                                                                                                                          | Adds                                                                                                                                                                                                        | Namespace          |\n| ----------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |\n| [`eslint-plugin-react-hooks`](https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Feslint-plugin-react-hooks) (v6 or v7)                                               | The React Compiler frontend's correctness rules — fired when a React Compiler is detected in the project.                                                                                                   | `react-hooks-js\u002F*` |\n| [`eslint-plugin-react-you-might-not-need-an-effect`](https:\u002F\u002Fgithub.com\u002Fnickjvandyke\u002Feslint-plugin-react-you-might-not-need-an-effect) (v0.10+) | Complementary effects-as-anti-pattern rules (`no-derived-state`, `no-chain-state-updates`, `no-event-handler`, `no-pass-data-to-parent`, …) that run alongside React Doctor's native State & Effects rules. | `effect\u002F*`         |\n\n### Inline suppressions\n\n```tsx\n\u002F\u002F react-doctor-disable-next-line react-doctor\u002Fno-cascading-set-state\nuseEffect(() => {\n  setA(value);\n  setB(value);\n}, [value]);\n```\n\nWhen two rules fire on the same line, you have two equivalent options. Comma-separate the rule ids on a single comment:\n\n```tsx\n\u002F\u002F react-doctor-disable-next-line react-doctor\u002Frerender-state-only-in-handlers, react-doctor\u002Fno-derived-useState\nconst [localSearch, setLocalSearch] = useState(searchQuery);\n```\n\nOr stack one comment per rule directly above the diagnostic. Stacked comments are honored as long as nothing but other `react-doctor-disable-next-line` comments sits between them and the target line:\n\n```tsx\n\u002F\u002F react-doctor-disable-next-line react-doctor\u002Frerender-state-only-in-handlers\n\u002F\u002F react-doctor-disable-next-line react-doctor\u002Fno-derived-useState\nconst [localSearch, setLocalSearch] = useState(searchQuery);\n```\n\nA code line between stacked comments breaks the chain: only the comment immediately above the diagnostic (and any contiguous `react-doctor-disable-next-line` comments stacked on top of it) is honored. If a comment looks adjacent but the rule still fires, run `react-doctor --explain \u003Cfile:line>` — it reports whether a nearby suppression was found, what rules it covers, and why it didn't apply.\n\nBlock comments work inside JSX:\n\n\u003C!-- prettier-ignore -->\n```tsx\n{\u002F* react-doctor-disable-next-line react\u002Fno-danger *\u002F}\n\u003Cdiv dangerouslySetInnerHTML={{ __html }} \u002F>\n```\n\nFor multi-line JSX, putting the comment immediately above the opening tag covers the entire attribute list (matching ESLint convention).\n\n## Lint plugin (standalone)\n\nThe same rule set ships as both an oxlint plugin and an ESLint plugin, so you can wire it into whichever lint engine your project already runs. These are published as separate packages, so you can install just the lint integration without pulling in the full CLI.\n\n**oxlint** in `.oxlintrc.json` (install [`oxlint-plugin-react-doctor`](https:\u002F\u002Fnpmjs.com\u002Fpackage\u002Foxlint-plugin-react-doctor)):\n\n```jsonc\n{\n  \"jsPlugins\": [{ \"name\": \"react-doctor\", \"specifier\": \"oxlint-plugin-react-doctor\" }],\n  \"rules\": {\n    \"react-doctor\u002Fno-fetch-in-effect\": \"warn\",\n    \"react-doctor\u002Fno-derived-state-effect\": \"warn\",\n  },\n}\n```\n\n**ESLint** flat config (install [`eslint-plugin-react-doctor`](https:\u002F\u002Fnpmjs.com\u002Fpackage\u002Feslint-plugin-react-doctor)):\n\n```js\nimport reactDoctor from \"eslint-plugin-react-doctor\";\n\nexport default [\n  reactDoctor.configs.recommended,\n  reactDoctor.configs.next,\n  reactDoctor.configs[\"react-native\"],\n  reactDoctor.configs[\"tanstack-start\"],\n  reactDoctor.configs[\"tanstack-query\"],\n];\n```\n\nThe full rule list lives in [`packages\u002Foxlint-plugin-react-doctor\u002Fsrc\u002Fplugin\u002Frules`](https:\u002F\u002Fgithub.com\u002Fmillionco\u002Freact-doctor\u002Ftree\u002Fmain\u002Fpackages\u002Foxlint-plugin-react-doctor\u002Fsrc\u002Fplugin\u002Frules).\n\n## CLI reference\n\n```\nUsage: react-doctor [directory] [options]\n\nOptions:\n  -v, --version           display the version number\n  --no-lint               skip linting\n  --verbose               show every rule and per-file details (default shows top 3 rules)\n  --score                 output only the score\n  --json                  output a single structured JSON report\n  -y, --yes               skip prompts, scan all workspace projects\n  --full                  skip prompts, always run a full scan\n  --project \u003Cname>        select workspace project (comma-separated for multiple)\n  --diff [base]           scan only files changed vs base branch\n  --staged                scan only staged files (for pre-commit hooks)\n  --offline               skip the score API and share URL (no score shown)\n  --fail-on \u003Clevel>       exit with error on diagnostics: error, warning, none\n  --annotations           output diagnostics as GitHub Actions annotations\n  --pr-comment            tune CLI output for sticky PR comments (drops design\n                          cleanup from the printed list and fail-on gate)\n  --explain \u003Cfile:line>   diagnose why a rule fired or why a suppression didn't apply\n  --why \u003Cfile:line>       alias for --explain\n  -h, --help              display help\n```\n\nWhen a suppression isn't working, `--explain \u003Cfile:line>` (or its alias `--why \u003Cfile:line>`) reports what the scanner sees at that location, including why a nearby `react-doctor-disable-next-line` didn't apply. The diagnosis distinguishes the common failure modes — adjacent comment for a different rule (use the comma form), a code line between the comment and the diagnostic (the chain is broken), or no nearby suppression at all. The same hint surfaces inline with `--verbose` for every flagged site, and in `--json` output as `diagnostic.suppressionHint`, so a single scan doubles as a suppression audit without a separate flag.\n\n`--json` produces a parsable object on stdout with all human-readable output suppressed. Errors still produce a JSON object with `ok: false`, so stdout is always a valid document.\n\n### Config keys\n\n| Key                        | Type                             | Default  |\n| -------------------------- | -------------------------------- | -------- |\n| `ignore.rules`             | `string[]`                       | `[]`     |\n| `ignore.files`             | `string[]`                       | `[]`     |\n| `ignore.overrides`         | `{ files, rules? }[]`            | `[]`     |\n| `lint`                     | `boolean`                        | `true`   |\n| `verbose`                  | `boolean`                        | `false`  |\n| `diff`                     | `boolean \\| string`              |          |\n| `failOn`                   | `\"error\" \\| \"warning\" \\| \"none\"` | `\"none\"` |\n| `customRulesOnly`          | `boolean`                        | `false`  |\n| `share`                    | `boolean`                        | `true`   |\n| `offline`                  | `boolean`                        | `false`  |\n| `textComponents`           | `string[]`                       | `[]`     |\n| `rawTextWrapperComponents` | `string[]`                       | `[]`     |\n| `serverAuthFunctionNames`  | `string[]`                       | `[]`     |\n| `respectInlineDisables`    | `boolean`                        | `true`   |\n| `adoptExistingLintConfig`  | `boolean`                        | `true`   |\n| `ignore.tags`              | `string[]`                       | `[]`     |\n\n`textComponents` is the broad escape hatch for `rn-no-raw-text` — list components that themselves behave like React Native's `\u003CText>` (custom `Typography`, `NativeTabs.Trigger.Label`, etc.) and the rule will treat them as text containers regardless of what their children look like.\n\n`rawTextWrapperComponents` is the narrower option for components that are not text elements but safely route string-only children through an internal `\u003CText>` (e.g. `heroui-native`'s `Button`, which stringifies its children and renders them through a `ButtonLabel`). Listed wrappers suppress `rn-no-raw-text` only when their children are entirely stringifiable. A wrapper with mixed children — e.g. `\u003CButton>Save\u003CIcon \u002F>\u003C\u002FButton>` — still reports because the wrapper can't safely route raw text alongside a sibling JSX element.\n\n`serverAuthFunctionNames` teaches `server-auth-actions` about custom auth guards your codebase wraps around its auth library (e.g. `requireWorkspaceMember`, `ensureSignedIn`). Listed names are accepted as a valid top-of-action auth check whether called bare (`requireWorkspaceMember()`) or as a member (`guards.requireWorkspaceMember()`), and — unlike the built-in default list — are treated as distinctive so the receiver is not re-validated.\n\n`ignore.tags` suppresses entire categories of rules by tag. For example, `\"tags\": [\"design\"]` disables all opinionated design rules (gradient text, pure black backgrounds, side tab borders, default Tailwind palettes). Available tags: `\"design\"`.\n\n`offline` skips the score API entirely — no score is shown and no share URL is generated. Automatically enabled in CI environments (GitHub Actions, GitLab CI, CircleCI) so CI runs don't depend on the network.\n\n### React Native rules in mixed monorepos\n\n`rn-*` rules respect per-package boundaries automatically. In a mixed React Native + web monorepo (`apps\u002Fmobile` alongside `apps\u002Fweb` \u002F `apps\u002Fvite-app` \u002F `packages\u002Fstorybook` \u002F `apps\u002Fdocs`), every `rn-*` rule walks up to the file's nearest `package.json` before running:\n\n- Packages that declare `react-native`, `react-native-tvos`, `expo`, `expo-router`, `@expo\u002F*`, `react-native-windows`, `react-native-macos`, anything under the `@react-native\u002F` or `@react-native-` namespaces (`@react-native-firebase\u002Fapp`, `@react-native-async-storage\u002Fasync-storage`, …), or Metro's top-level `\"react-native\"` resolution field → rules ON.\n- Packages that declare a web-only framework (`next`, `vite`, `react-scripts`, `gatsby`, `@remix-run\u002F*`, `@docusaurus\u002F*`, `@storybook\u002F*`, or plain `react-dom` without an RN sibling) → rules OFF.\n- Packages with no clear local signal → fall back to the project-level framework detection.\n\nFile extensions override the package classification when they're unambiguous: `*.web.tsx` \u002F `*.web.jsx` are always skipped (Metro resolves these only against `react-native-web`); `*.ios.tsx` \u002F `*.android.tsx` \u002F `*.native.tsx` are always scanned (mobile-only).\n\nThe detection is bidirectional: a web-rooted monorepo (root `package.json` declares `next` or `vite`) still loads the `rn-*` rules when any workspace targets React Native — the file-level boundary then keeps them silent on the web workspaces and active on the mobile ones.\n\n`rn-no-raw-text` additionally short-circuits raw text inside platform-fork branches:\n\n- `if (Platform.OS === \"web\") { … }` consequent — and the `else` branch of `if (Platform.OS !== \"web\")`.\n- `Platform.OS === \"web\" ? \u003CX \u002F> : …` ternaries, `Platform.OS === \"web\" && \u003CX \u002F>` short-circuits, and the reversed-operand form `\"web\" === Platform.OS`.\n- `switch (Platform.OS) { case \"web\": … }` case bodies (other cases still report).\n- `Platform.select({ web: \u003CX \u002F>, default: \u003CY \u002F> })` — only the `web` arm is exempt.\n- `Platform?.OS === \"web\"` (optional chain) and `Platform.OS! === \"web\"` (TS non-null assertion) parse the same way as the bare form.\n\nThe walker stops at function and `Program` boundaries — JSX defined inside a callback hoisted out of a `Platform.OS` branch does not inherit the parent guard. Negative platform checks like `Platform.OS === \"ios\"` are deliberately NOT treated as web exemptions; only the explicit web branch is.\n\n## Scoring\n\nThe health score formula: `100 - (unique_error_rules x 1.5) - (unique_warning_rules x 0.75)`.\n\nScoring runs on react.doctor's API and is **network-dependent**: without a successful API round-trip (or under `--offline`) the score is omitted and the rest of the report still renders normally. Key details:\n\n- The score counts **unique rules triggered**, not total instances. Fixing 49 of 50 `no-barrel-import` violations does not change the score; fixing all 50 removes the 0.75 penalty for that rule.\n- Error-severity rules cost 1.5 points each. Warning-severity rules cost 0.75 points each.\n- Category breakdowns shown in the output are for display only and do not weight the score.\n\nScore labels: 75+ is **Great**, 50 to 74 is **Needs work**, under 50 is **Critical**.\n\nScores may decrease across releases as new rules are added. Each new rule that fires in your codebase introduces an additional penalty. This is expected — it means the tool is catching more issues, not that your code got worse. Pin to a specific react-doctor version in CI if you need stable scores across upgrades.\n\n## Diff and staged modes\n\nReact Doctor can scan only changed files instead of the full project:\n\n- **`--diff [base]`** scans files changed vs a base branch. Auto-detects `main`\u002F`master`, or pass an explicit branch: `--diff develop`. Also available as a config key: `\"diff\": true` or `\"diff\": \"develop\"`.\n- **`--staged`** scans only files in the git staging area (index). Designed for pre-commit hooks — materializes staged file contents into a temp directory so the scan reflects exactly what will be committed.\n- **`--full`** forces a full scan, overriding any `diff` value in config or CLI.\n\nWhen on a feature branch without explicit flags, you'll be prompted: \"Only scan changed files?\" This prompt is suppressed in CI, `--json` mode, and non-interactive environments.\n\n`--staged` and `--diff` cannot be combined.\n\n## Agent and CI integration\n\nReact Doctor detects 50+ coding agents (Claude Code, Cursor, Codex, OpenCode, Windsurf, and more) and adapts its behavior automatically:\n\n- **Install for agents**: `npx react-doctor@latest install` writes agent-specific rule files (SKILL.md, AGENTS.md, .cursorrules) into your project so agents learn React best practices.\n- **JSON output**: `--json` produces a structured `JsonReport` on stdout. Errors still produce a valid JSON document with `ok: false`. Use `--json-compact` for minimal whitespace.\n- **Score-only output**: `--score` outputs just the numeric score (0-100), useful for threshold checks in agent loops.\n- **GitHub Actions annotations**: `--annotations` emits `::error` \u002F `::warning` format for inline PR annotations. Annotations don't change the exit code.\n- **Exit codes**: `--fail-on error` (default) exits non-zero when error-severity diagnostics are found. Use `--fail-on warning` or `--fail-on none` to tune CI gating. See [PR blocking and exit codes](#pr-blocking-and-exit-codes) for the full model — including how to fail only on new regressions vs. fail on the baseline score.\n- **Programmatic API**: `import { diagnose } from \"react-doctor\u002Fapi\"` for direct integration in scripts and automation.\n\nIn CI environments, prompts are automatically skipped and `--offline` is implied (no network round-trip; score is omitted from the output).\n\n## Node.js API\n\n```js\nimport { diagnose, toJsonReport, summarizeDiagnostics } from \"react-doctor\u002Fapi\";\n\nconst result = await diagnose(\".\u002Fpath\u002Fto\u002Fyour\u002Freact-project\");\n\nconsole.log(result.score); \u002F\u002F { score: 82, label: \"Great\" } or null\nconsole.log(result.diagnostics); \u002F\u002F Diagnostic[]\nconsole.log(result.project); \u002F\u002F detected framework, React version, etc.\n```\n\n`diagnose` accepts a second argument: `{ lint?: boolean }`.\n\n```js\nconst report = toJsonReport(result, { version: \"1.0.0\" });\nconst counts = summarizeDiagnostics(result.diagnostics);\n```\n\n`react-doctor\u002Fapi` re-exports `JsonReport`, `JsonReportSummary`, `JsonReportProjectEntry`, `JsonReportMode`, plus the lower-level `buildJsonReport` and `buildJsonReportError` builders. See [`packages\u002Freact-doctor\u002Fsrc\u002Fapi.ts`](https:\u002F\u002Fgithub.com\u002Fmillionco\u002Freact-doctor\u002Fblob\u002Fmain\u002Fpackages\u002Freact-doctor\u002Fsrc\u002Fapi.ts) for the full types.\n\n## Leaderboard\n\nTop React codebases scanned by React Doctor, ranked by score. Updated automatically from [millionco\u002Freact-doctor-benchmarks](https:\u002F\u002Fgithub.com\u002Fmillionco\u002Freact-doctor-benchmarks).\n\n\u003C!-- LEADERBOARD:START -->\n\u003C!-- prettier-ignore -->\n| #  | Repo | Score |\n| -- | ---- | ----: |\n| 1  | [executor](https:\u002F\u002Fgithub.com\u002FRhysSullivan\u002Fexecutor) | 94 |\n| 2  | [nodejs.org](https:\u002F\u002Fgithub.com\u002Fnodejs\u002Fnodejs.org) | 86 |\n| 3  | [tldraw](https:\u002F\u002Fgithub.com\u002Ftldraw\u002Ftldraw) | 70 |\n| 4  | [t3code](https:\u002F\u002Fgithub.com\u002Fpingdotgg\u002Ft3code) | 68 |\n| 5  | [better-auth](https:\u002F\u002Fgithub.com\u002Fbetter-auth\u002Fbetter-auth) | 64 |\n| 6  | [excalidraw](https:\u002F\u002Fgithub.com\u002Fexcalidraw\u002Fexcalidraw) | 63 |\n| 7  | [mastra](https:\u002F\u002Fgithub.com\u002Fmastra-ai\u002Fmastra) | 63 |\n| 8  | [payload](https:\u002F\u002Fgithub.com\u002Fpayloadcms\u002Fpayload) | 60 |\n| 9  | [typebot](https:\u002F\u002Fgithub.com\u002FbaptisteArno\u002Ftypebot.io) | 57 |\n| 10 | [plane](https:\u002F\u002Fgithub.com\u002Fmakeplane\u002Fplane) | 56 |\n\n\u003C!-- LEADERBOARD:END -->\n\nSee the [full leaderboard](https:\u002F\u002Fwww.react.doctor\u002Fleaderboard).\n\n## Resources & Contributing Back\n\nWant to try it out? Check out [the demo](https:\u002F\u002Freact.doctor).\n\nLooking to contribute back? Clone the repo, install, build, and submit a PR.\n\n```bash\ngit clone https:\u002F\u002Fgithub.com\u002Fmillionco\u002Freact-doctor\ncd react-doctor\npnpm install\npnpm build\nnode packages\u002Freact-doctor\u002Fbin\u002Freact-doctor.js \u002Fpath\u002Fto\u002Fyour\u002Freact-project\n```\n\nFind a bug? Head to the [issue tracker](https:\u002F\u002Fgithub.com\u002Fmillionco\u002Freact-doctor\u002Fissues).\n\n### License\n\nReact Doctor is MIT-licensed open-source software.\n","React Doctor 是一个用于检测和改进 React 代码质量的工具。它通过扫描代码库并提供0到100的健康评分，帮助开发者识别潜在问题，包括状态管理、性能优化、架构设计、安全性和可访问性等方面，并给出具体的改进建议。该工具支持 Next.js、Vite 和 React Native 等主流框架和技术栈。适用于需要提高React应用开发质量和维护性的场景，无论是个人开发者还是团队协作环境都能从中受益。此外，React Doctor还能够与多种编码助手集成，确保这些助手生成的代码符合最佳实践标准。",2,"2026-06-11 03:32:18","trending"]