Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
0be354a
fix
aidenybai May 12, 2026
0d6c14a
fix
aidenybai May 12, 2026
d6f5362
fix
aidenybai May 12, 2026
99e40a2
fix
aidenybai May 12, 2026
81400ab
fix
aidenybai May 12, 2026
3b098da
fix
aidenybai May 12, 2026
751bbd2
improve v2 rule precision based on user feedback
aidenybai May 13, 2026
1046f0f
wip: parity work, score module, proxy fetch
skoshx May 13, 2026
a910530
improve v2 rule precision, score formula, CLI features, and parity
aidenybai May 13, 2026
6b6387a
fix radix-aschild-single-child FP on JSXExpressionContainer children
aidenybai May 13, 2026
7d5b037
fix
aidenybai May 13, 2026
1b606ad
fix rn-no-raw-text FP on web-only directories (docusaurus, docs, stor…
aidenybai May 13, 2026
f1badce
fix
aidenybai May 13, 2026
d801dff
fix radix-aschild-single-child FP on JSX comments
aidenybai May 13, 2026
9ab1832
fix effect-needs-cleanup FP: add 'remove' to unsubscription methods
aidenybai May 13, 2026
3b8b077
add test-noise tags to no-nested-component-definition and no-eval
aidenybai May 13, 2026
fabb0a8
extend OG_IMAGE_FILE_PATTERN for Remix-style opengraph routes
aidenybai May 13, 2026
df4b733
fix FPs found in deep issue-by-issue audit across 100 repos
aidenybai May 13, 2026
ff90709
fix client-event-listeners + js-request-idle-callback FPs
aidenybai May 13, 2026
68cd83f
fix
aidenybai May 13, 2026
a8f7473
fix
aidenybai May 13, 2026
24e0946
fix
aidenybai May 13, 2026
50f2623
fix
aidenybai May 13, 2026
b8bbd40
fix: declare picomatch dep so vite-plus-core resolves on Vercel
aidenybai May 13, 2026
ed30791
fix: inject picomatch into vite-plus-core via pnpm.packageExtensions
aidenybai May 13, 2026
06621ce
fix: address Cursor Bugbot findings
aidenybai May 13, 2026
556f7d5
chore: remove parity.ts script
aidenybai May 13, 2026
cfc65f2
feat!: swap packages/react-doctor with v2 rewrite
aidenybai May 13, 2026
266d9f2
fix
aidenybai May 13, 2026
840a6d5
chore: retrigger pkg.pr.new
aidenybai May 13, 2026
c5f9f0e
fix
aidenybai May 13, 2026
a370a32
fix
aidenybai May 13, 2026
32cfd42
fix
aidenybai May 13, 2026
6311c54
fix: proper categorization
skoshx May 13, 2026
1cb10cd
fix: false-positive reduction, per-category scoring cap, framework ex…
aidenybai May 13, 2026
b1057e2
test: relax rule-metadata category assertion to expect.any(String)
aidenybai May 13, 2026
122c367
fix: address PR review feedback for CLI and oxlint formatting
aidenybai May 13, 2026
f457aa2
fix: pass loadedConfig in staged mode and avoid empty diff scan
aidenybai May 13, 2026
2b5310e
fix: remove dead diff branch in resolveIncludePaths and guard prompt …
aidenybai May 13, 2026
00a7cc7
fix: resolve boolean inspect options eagerly so config values are hon…
aidenybai May 13, 2026
6d8fb70
fix: strip rootDir in staged mode so scan stays on the materialized s…
aidenybai May 13, 2026
1c305c6
fix: improved formatting for warnings and errors
skoshx May 13, 2026
880d6f2
fix
aidenybai May 13, 2026
7797985
docs(changeset): document v2 compat re-exports, category routing fix,…
aidenybai May 13, 2026
dc5f1f8
fix
aidenybai May 13, 2026
af87857
revert: drop --prerelease tweak from release workflow
aidenybai May 13, 2026
178ff44
pub
aidenybai May 13, 2026
3abd2a0
pub
aidenybai May 13, 2026
c9c3cc9
pub
aidenybai May 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
14 changes: 14 additions & 0 deletions .changeset/pre.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"mode": "pre",
"tag": "beta",
"initialVersions": {
"react-doctor": "0.1.6",
"website": "0.1.0"
},
"changesets": [
"tailwind-version-detection",
"url-asset-imports-and-info-annotations",
"v2-categorization-and-compat",
"v2-rewrite"
]
}
39 changes: 39 additions & 0 deletions .changeset/url-asset-imports-and-info-annotations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
"react-doctor": patch
---

Temporarily disable the codebase graph checks (dead-code, dependencies,
react-architecture) by default in the CLI. They still produce too many
false positives on large/monorepo codebases (e.g. PostHog: 14k+ warnings,
1.6k errors driven by `unresolved-import`, `unused-export`, etc.) to be
acceptable as default-on diagnostics. Opt back in per-run with
`--dead-code` or persistently via `"reactDoctor": { "deadCode": true }`
in `package.json` / `react-doctor.config.json`. The SDK behavior is
unchanged (it was already opt-in there).

Fix three false-positive sources that surfaced when the graph is enabled:

- The codebase analyzer's extractor records every
`new URL(specifier, import.meta.url)` as an asset import, but the
resolver still ran ordinary module resolution against the specifier.
Idiomatic Node config patterns like
`fileURLToPath(new URL("./src", import.meta.url))` therefore emitted
an `unresolved-import` error even though the URL is only used for
path computation. Asset URLs that don't resolve as modules are now
treated as silent asset references (tracking the file path when it
exists on disk so dead-code detection still sees the reference).
- `--annotations` mapped every non-error severity to `::warning`, which
promoted `info` diagnostics (e.g. the demoted `unused-type-export`
rule) to warning-level CI annotations. Info diagnostics are exempt
from scoring and meant to surface only in `--verbose`; they are now
skipped entirely from GitHub Actions annotations.
- The `vite` and `nextjs` codebase plugins registered
`vite.config.{*}` and `next.config.{*}` as **runtime** entrypoints,
which made their build-time plugin imports
(`@vitejs/plugin-react`, `@tailwindcss/vite`, `@next/mdx`, ...) look
like runtime dependencies and triggered `runtime-dev-dependency`
warnings on packages that are correctly declared in devDependencies.
Both plugins now leave config files to be picked up as `support`
entries by the generic `*.config.*` rule in `SUPPORT_ENTRY_PATTERNS`
— plugin imports are still tracked as used dependencies, but no
longer count as runtime usage.
31 changes: 31 additions & 0 deletions .changeset/v2-categorization-and-compat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
"react-doctor": patch
---

Fixes carried out alongside the v2 rewrite:

- **Restore the v1 error-class surface on `react-doctor/api`.** The compat
module now re-exports `AmbiguousProjectError`, `NoReactDependencyError`,
`PackageJsonNotFoundError`, `ProjectNotFoundError`, `ReactDoctorError`, and
`isReactDoctorError` so existing v1 consumers (sandbox runners,
third-party diagnose wrappers) keep importing the same names.
- **Fix oxlint category routing.** The runner had a stale duplicate
`RULE_CATEGORY_MAP` that covered only ~half the v2 rules; the other half
(`tailwind-*`, `client-*`, `effect-*`, `nextjs-*`, `tanstack-*`, `rn-*`,
many `no-*`, …) silently fell through to the `Other` category. Switched
the runner to the comprehensive `resolveOxlintDiagnosticCategory()` that
already existed in `core/rules/lint/utils`, deleted the duplicated map,
and added a registry test that asserts every rule resolves to a real
category (never `Other`). Category breakdowns now look meaningful:
Performance/Architecture/Accessibility/State & Effects/etc. instead of a
giant `Other` bucket.
Scoring calibration note: v2's local score function is more expressive
than v1's (`100 - errorRules*1.5 - warningRules*0.75`) — per-category
caps and log-scaled per-rule amplification mean high-instance rules cost
more and many small categories cost more than v1's flat per-unique-rule
penalty. The same project will score lower under v2 than v1 even when v2
finds fewer total issues. The remote `react.doctor` score endpoint
should be updated to use this package's `react-doctor/score` export so
server and local results match; until then, the remote API will reject
the v2 payload shape and clients will silently fall back to local v2
scoring.
13 changes: 13 additions & 0 deletions .changeset/v2-rewrite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"react-doctor": minor
---

v2 rewrite: SDK-first surface (`react-doctor` exports the SDK; the legacy
`diagnose()` shape lives at `react-doctor/api`). Adds a new
`react-doctor/score` subpath. Drops the `react-doctor/browser-poc` export.
Drops the `eslint-plugin-react-hooks` /
`eslint-plugin-react-you-might-not-need-an-effect` peer dependencies
(`eslint-plugin-react-hooks` is now a regular dependency; the
"you-might-not-need-an-effect" rules are skipped unless the plugin is
installed in the consumer project). Other runtime deps trimmed: `knip`,
`bippy`, `@oxc-parser/wasm` dropped; `oxc-parser`, `oxc-resolver` added.
30 changes: 30 additions & 0 deletions .github/workflows/preview-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Preview Release

on:
push:
branches: [main]
pull_request:
branches: [main]

permissions: {}

jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5

- uses: pnpm/action-setup@v4

- uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm

- run: pnpm install --frozen-lockfile

- name: Build react-doctor
run: pnpm --filter react-doctor build

- name: Publish preview release
run: pnpm exec pkg-pr-new publish './packages/react-doctor'
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ review-report.md
review-*.md
*.review.md
*.tgz
.regression/
10 changes: 9 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,13 @@
"@changesets/cli": "^2.31.0",
"@types/node": "^25.6.0",
"@voidzero-dev/vite-plus-core": "^0.1.15",
"pkg-pr-new": "^0.0.71",
"turbo": "^2.9.7",
"typescript": "^6.0.3",
"vite-plus": "^0.1.15"
},
"engines": {
"node": ">=22",
"node": ">=22.12.0",
"pnpm": ">=8"
},
"packageManager": "pnpm@10.29.1",
Expand All @@ -48,6 +49,13 @@
"overrides": {
"oxlint": "^1.63.0",
"oxlint-tsgolint": "^0.22.1"
},
"packageExtensions": {
"@voidzero-dev/vite-plus-core": {
"dependencies": {
"picomatch": "^4.0.4"
}
}
}
}
}
97 changes: 97 additions & 0 deletions packages/react-doctor/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,102 @@
# react-doctor

## 0.2.0-beta.1

### Patch Changes

- [#217](https://github.com/millionco/react-doctor/pull/217) [`3abd2a0`](https://github.com/millionco/react-doctor/commit/3abd2a0b1ca47245d380720e428070718912db66) Thanks [@aidenybai](https://github.com/aidenybai)! - Temporarily disable the codebase graph checks (dead-code, dependencies,
react-architecture) by default in the CLI. They still produce too many
false positives on large/monorepo codebases (e.g. PostHog: 14k+ warnings,
1.6k errors driven by `unresolved-import`, `unused-export`, etc.) to be
acceptable as default-on diagnostics. Opt back in per-run with
`--dead-code` or persistently via `"reactDoctor": { "deadCode": true }`
in `package.json` / `react-doctor.config.json`. The SDK behavior is
unchanged (it was already opt-in there).

Fix three false-positive sources that surfaced when the graph is enabled:

- The codebase analyzer's extractor records every
`new URL(specifier, import.meta.url)` as an asset import, but the
resolver still ran ordinary module resolution against the specifier.
Idiomatic Node config patterns like
`fileURLToPath(new URL("./src", import.meta.url))` therefore emitted
an `unresolved-import` error even though the URL is only used for
path computation. Asset URLs that don't resolve as modules are now
treated as silent asset references (tracking the file path when it
exists on disk so dead-code detection still sees the reference).
- `--annotations` mapped every non-error severity to `::warning`, which
promoted `info` diagnostics (e.g. the demoted `unused-type-export`
rule) to warning-level CI annotations. Info diagnostics are exempt
from scoring and meant to surface only in `--verbose`; they are now
skipped entirely from GitHub Actions annotations.
- The `vite` and `nextjs` codebase plugins registered
`vite.config.{*}` and `next.config.{*}` as **runtime** entrypoints,
which made their build-time plugin imports
(`@vitejs/plugin-react`, `@tailwindcss/vite`, `@next/mdx`, ...) look
like runtime dependencies and triggered `runtime-dev-dependency`
warnings on packages that are correctly declared in devDependencies.
Both plugins now leave config files to be picked up as `support`
entries by the generic `*.config.*` rule in `SUPPORT_ENTRY_PATTERNS`
— plugin imports are still tracked as used dependencies, but no
longer count as runtime usage.

## 0.2.0-beta.0

### Minor Changes

- [#217](https://github.com/millionco/react-doctor/pull/217) [`cfc65f2`](https://github.com/millionco/react-doctor/commit/cfc65f28c8ccd3e540cdacce97574dd65b99ef19) Thanks [@aidenybai](https://github.com/aidenybai)! - v2 rewrite: SDK-first surface (`react-doctor` exports the SDK; the legacy
`diagnose()` shape lives at `react-doctor/api`). Adds a new
`react-doctor/score` subpath. Drops the `react-doctor/browser-poc` export.
Drops the `eslint-plugin-react-hooks` /
`eslint-plugin-react-you-might-not-need-an-effect` peer dependencies
(`eslint-plugin-react-hooks` is now a regular dependency; the
"you-might-not-need-an-effect" rules are skipped unless the plugin is
installed in the consumer project). Other runtime deps trimmed: `knip`,
`bippy`, `@oxc-parser/wasm` dropped; `oxc-parser`, `oxc-resolver` added.

### Patch Changes

- [#202](https://github.com/millionco/react-doctor/pull/202) [`53fa4df`](https://github.com/millionco/react-doctor/commit/53fa4dffe837e0157fb850fef700fccaaec191ea) Thanks [@aidenybai](https://github.com/aidenybai)! - Detect the project's Tailwind version (`tailwindcss` in `package.json`,
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 version cannot be resolved (the same
"assume latest" fallback used by the React-major gate).

A new `tailwindVersion` field is added to `ProjectInfo` and printed
during scans so it's visible alongside the detected React version and
framework.

- [#217](https://github.com/millionco/react-doctor/pull/217) [`7797985`](https://github.com/millionco/react-doctor/commit/77979851d288e29b19f808168742e1b15e5ec8ae) Thanks [@aidenybai](https://github.com/aidenybai)! - Fixes carried out alongside the v2 rewrite:

- **Restore the v1 error-class surface on `react-doctor/api`.** The compat
module now re-exports `AmbiguousProjectError`, `NoReactDependencyError`,
`PackageJsonNotFoundError`, `ProjectNotFoundError`, `ReactDoctorError`, and
`isReactDoctorError` so existing v1 consumers (sandbox runners,
third-party diagnose wrappers) keep importing the same names.
- **Fix oxlint category routing.** The runner had a stale duplicate
`RULE_CATEGORY_MAP` that covered only ~half the v2 rules; the other half
(`tailwind-*`, `client-*`, `effect-*`, `nextjs-*`, `tanstack-*`, `rn-*`,
many `no-*`, …) silently fell through to the `Other` category. Switched
the runner to the comprehensive `resolveOxlintDiagnosticCategory()` that
already existed in `core/rules/lint/utils`, deleted the duplicated map,
and added a registry test that asserts every rule resolves to a real
category (never `Other`). Category breakdowns now look meaningful:
Performance/Architecture/Accessibility/State & Effects/etc. instead of a
giant `Other` bucket.
Scoring calibration note: v2's local score function is more expressive
than v1's (`100 - errorRules*1.5 - warningRules*0.75`) — per-category
caps and log-scaled per-rule amplification mean high-instance rules cost
more and many small categories cost more than v1's flat per-unique-rule
penalty. The same project will score lower under v2 than v1 even when v2
finds fewer total issues. The remote `react.doctor` score endpoint
should be updated to use this package's `react-doctor/score` export so
server and local results match; until then, the remote API will reject
the v2 payload shape and clients will silently fall back to local v2
scoring.

## 0.1.6

### Patch Changes
Expand Down
Loading
Loading