205 Commits

Author SHA1 Message Date
Aiden Bai 03a94351c7 Address review-report.md findings + self-review regressions (#140) v0.2.3 2026-04-30 23:12:50 -07:00
Aiden Bai 301e121a23 chore: update project configuration and dependencies
- Added `.turbo` to `.gitignore`.
- Removed obsolete `.oxlintrc.json` configuration file.
- Updated `package.json` to use `turbo` for script commands and added new `typecheck` and `check` scripts.
- Updated `devDependencies` to include `@voidzero-dev/vite-plus-core` and `turbo`, while removing `oxlint` and `tsdown`.
- Adjusted `pnpm-lock.yaml` to reflect new dependencies and overrides for `vite` and `vitest`.
- Removed `tsdown.config.ts` and `vitest.config.ts` files from `react-doctor` package.
- Refactored interfaces in `src/types.ts`, `src/core`, and `src/plugin` files for consistency.
- Cleaned up unused imports and code in various utility files.
- Deleted unused `react-doctor-icon.tsx` component from the website package.
2026-04-29 04:47:49 -07:00
Aiden Bai 9e9769d10b 0.0.42 2026-04-28 20:40:22 -07:00
Aiden Bai 79fb8776f9 fix(react-doctor): walk error cause chain when retrying knip plugin failures (#137)
The retry logic in `runKnip` only inspected the top-level error message
and required a leading `/` before the plugin name. Knip 6.x wraps the
underlying failure in a `LoaderError` whose cause holds the actionable
detail, and on Windows the path separator can be `\`, so the regex
silently failed and dead code detection was skipped with no diagnostic.

The plugin-name extractor now walks the entire `error.cause` chain and
matches both POSIX and Windows separators (and bare filenames). Plugin
disable is guarded with `failedPlugin in parsedConfig` so a stray
`*.config.*` substring in an unrelated error can't pollute the parsed
knip config. The retry loop captures the latest error and rethrows it
after exhausting attempts, instead of throwing `Error("Unreachable")`.

Failures are now reported with the full chain via a new
`formatErrorChain` utility, e.g.:

    Error loading /repo/knip.ts → Cannot find module './missing'

The lint failure path uses the same formatter for parity.

Fixes #135
2026-04-28 20:12:12 -07:00
Aiden Bai 391b751103 fix(react-doctor): respect workspace-local knip config in monorepos (#136)
When a workspace owns its own knip config, run knip from the workspace
directory so it's discovered, instead of running from the monorepo root
with `--workspace` and silently falling back to knip's defaults. The
default-fallback path was mass-flagging every file as `Unused file` for
projects whose entry layout doesn't match knip's defaults (e.g. TanStack
Start, custom Vite setups).

Adds `hasKnipConfig` utility checking the standard `KNIP_CONFIG_LOCATIONS`
and a `runKnipForProject` helper that picks the correct cwd. Behavior for
monorepos that wire workspaces through a root-level `knip.json` is
unchanged.
2026-04-28 19:20:16 -07:00
Aiden Bai ff7eb45b81 0.0.41 2026-04-28 19:08:03 -07:00
Aiden Bai 1fdc9a0c0d fix 2026-04-28 19:05:27 -07:00
Aiden Bai 874f7bc816 0.0.40 2026-04-28 15:28:38 -07:00
Aiden Bai 83327cf5de minor cleanups 2026-04-28 15:27:19 -07:00
Aiden Bai 7da4ce4e27 fix(react-doctor): handle knip 6.x IssueRecords shape for files (#134)
Knip 6.x changed issues.files from Set<string> to an IssueRecords
object, causing runKnip to throw 'TypeError: issues.files is not
iterable' and silently skip dead-code detection.

Extract unused file paths through a dedicated collectUnusedFilePaths
utility that handles the 6.x object shape, legacy Set<string>, and
array forms. Also widen the KnipResults type to match.

Fixes #132

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
2026-04-23 00:51:59 -07:00
Cursor Agent b5519b6046 fix(react-doctor): inject browser scorer so bundles omit proxy-fetch
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
2026-04-18 03:58:00 +00:00
Cursor Agent 061a794936 feat(react-doctor): add browser entrypoint and diagnoseBrowser API
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
2026-04-18 03:53:07 +00:00
Cursor Agent 862d71a55c refactor(react-doctor): extract shared score helpers for browser reuse
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
2026-04-18 03:51:42 +00:00
Cursor Agent 466e8b505a refactor(react-doctor): shared buildDiagnoseTimedResult and browser diagnose
Extract merge/filter + score + elapsed timing into build-result. Browser
diagnose() accepts supplied lint/dead-code diagnostics and project metadata.
Worker re-exports diagnose; processBrowserDiagnostics uses the shared helper.

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
2026-04-18 03:46:41 +00:00
Cursor Agent 87b2640150 feat(react-doctor): add processBrowserDiagnostics for prebuilt diagnostics
Thin browser API that merges/filters caller-supplied diagnostics using
in-memory projectFiles, optional userConfig, and optional score override.
Re-export from worker entry for parity.

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
2026-04-18 03:44:16 +00:00
Cursor Agent 5238e7b238 feat(react-doctor): add browser entrypoint and shared diagnose core
Extract diagnose orchestration into diagnoseCore with injectable file reads
and optional environment diagnostics. Add react-doctor/browser and
react-doctor/worker exports plus diagnoseBrowser for in-memory projects.
Make proxyFetch skip npm execSync when process is unavailable.

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
2026-04-18 03:42:43 +00:00
Aiden Bai 5a96226608 fix: format README to pass CI 2026-04-17 02:02:22 -07:00
Aiden Bai 1d3d514a56 0.0.38 react-doctor@0.0.38 2026-04-16 21:06:19 -07:00
Aiden Bai bb5188ff8d docs: document all config options in README
Add failOn, customRulesOnly, share, and textComponents to the config
options table. Also note that diff: false disables auto-detection.
2026-04-16 21:06:12 -07:00
Aiden Bai 8b0485a128 fix: action improvements, clickable file paths, and website hydration fix
- action: add offline input, NO_COLOR for clean PR comments, fix diff
  mode on detached HEAD, update comments instead of delete+recreate
- scan: use file:line format so paths are clickable in VS Code
- website: fix terminal hydration mismatch by deferring localStorage read
v0.2.2
2026-04-16 21:02:47 -07:00
Aiden Bai 100731cb7b fix: format install-skill and detect-agents to pass CI 2026-04-16 20:02:51 -07:00
Aiden Bai f1bd77645c 0.0.37 2026-04-16 05:42:31 -07:00
Aiden Bai 69eead3c43 0.0.36 react-doctor@0.0.37 2026-04-16 05:40:33 -07:00
Aiden Bai 074f854d42 feat(react-doctor): add install subcommand for the react-doctor skill
Detects coding agents on PATH (Claude Code, Codex, Copilot, Gemini, Cursor,
OpenCode, Droid, Pi) and copies the bundled SKILL.md into each selected
agent's skills directory. Multiselect prompt by default; `--yes` skips it
and installs for every detected agent.
react-doctor@0.0.36
2026-04-16 05:39:24 -07:00
Aiden Bai ff5b6376d7 feat: add frontend design quality rules (#131)
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
2026-04-16 05:30:36 -07:00
Aiden Bai 7136aa59fe 0.0.35 2026-04-14 19:53:01 -07:00
Aiden Bai 455931005e 0.0.34 react-doctor@0.0.35 2026-04-14 19:52:25 -07:00
Aiden Bai 47105e9619 Fix false positives for Next.js redirect guidance and React Compiler detection (#129)
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
2026-04-14 19:23:54 -07:00
Aiden Bai 0eb635ac7f feat(react-doctor): add TanStack Start rules (#124)
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
2026-04-14 17:43:57 -07:00
Aiden Bai 87d4b86450 0.0.33 react-doctor@0.0.34 2026-04-10 22:15:50 -07:00
Aiden Bai e64ba023a6 0.0.32 react-doctor@0.0.33 2026-04-10 22:14:54 -07:00
Aiden Bai 901fce51b1 fix: restrict setter detection to direct Identifier callees (#122)
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-04-10 22:13:54 -07:00
Aiden Bai 10bd788323 fix: address multiple GitHub issues (#117, #113, #119, #106) (#120)
* fix: detect namespace hook calls (React.useState, React.useEffect) in all rules

Fixes #117: isHookCall now handles MemberExpression callee types
(e.g. React.useEffect, React.useState) in addition to plain Identifier
callee types. This fixes silent misses in no-derived-state-effect,
no-fetch-in-effect, no-cascading-set-state, no-effect-event-handler,
rerender-functional-setstate, rendering-hydration-no-flicker, and
the countSetStateCalls helper.

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>

* fix: remove unsupported jsx-a11y/no-noninteractive-element-interactions rule

Fixes #113, #119: This rule does not exist in the bundled oxlint
binary, causing 'Failed to parse oxlint configuration file' errors
and score suppression on all scans.

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>

* feat: add --no flag to skip prompts and always run full scan

Fixes #106: Adds -n/--no flag that skips all interactive prompts
(like --yes) but specifically declines the diff-only scan prompt,
ensuring a full project scan runs. Useful for CI agents that cannot
interact with TTY prompts.

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>

* test: add namespace hook detection tests (React.useEffect, React.useState, etc.)

Adds 13 tests verifying that all rules correctly detect namespace-style
hook calls (import * as React; React.useEffect, React.useState, etc.).
Covers: no-derived-state-effect, no-fetch-in-effect, no-cascading-set-state,
no-effect-event-handler, no-derived-useState, rerender-lazy-state-init,
rerender-functional-setstate, rerender-dependencies, rendering-hydration-no-flicker,
no-usememo-simple-expression, prefer-useReducer, plus a regression guard for
direct-import fixtures.

Relates to #117.

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>

* test: add edge case tests for project discovery and catalog resolution

Adds tests for:
- peerDependencies-only with catalog reference resolution
- Catalog reference to nonexistent catalog name (fallback to default)
- Empty pnpm-workspace.yaml handling
- Malformed package.json in workspace subdirectories
- Nonexistent root directory for discoverReactSubprojects
- File entries mixed with directory entries in project root

Relates to #101, #87, #105, #71.

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>

* fix: handle malformed JSON in workspace package.json files gracefully

readPackageJson now catches SyntaxError from malformed JSON and returns
an empty object instead of crashing. This prevents workspace discovery
from aborting when a subdirectory has an invalid package.json.

Previously, a broken package.json in any workspace subdirectory would
crash the entire scan with an unhandled SyntaxError.

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>

* feat: add customRulesOnly config to disable builtin react/jsx-a11y rules

Fixes #109: Teams with existing ESLint setups can now set
customRulesOnly: true in react-doctor.config.json to only run
react-doctor/* custom rules, skipping the bundled react/, jsx-a11y/,
react-perf, and react-compiler rules.

This avoids rule duplication for projects that already lint with
eslint-plugin-react, eslint-plugin-jsx-a11y, etc.

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>

* feat: inherit config from ancestor directories for monorepo support

Fixes #73: loadConfig now walks up the directory tree to find
react-doctor.config.json or package.json reactDoctor key from
ancestor directories. This means a single config at the monorepo
root applies to all workspace packages without needing to duplicate
the config in each package.

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>

* feat: add --annotations flag for GitHub Actions annotation output

Fixes #81: The --annotations flag outputs diagnostics in GitHub
Actions annotation format (::error / ::warning) so they appear
as inline annotations on PR diffs. Example:
  ::error file=src/App.tsx,line=42,title=react-doctor/no-fetch-in-effect::fetch() inside useEffect

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>

* refactor: remove Ami integration

Removes all Ami-related code:
- --ami flag, --fix flag, fix subcommand, install-ami subcommand
- Ami detection, installation, deeplinks, and open-URL helpers
- Fix prompt (maybePromptFix) with Ami banners
- Skill installation prompt (maybePromptSkillInstall)
- fetchEstimatedScore and EstimatedScoreResult
- AMI_WEBSITE_URL, AMI_INSTALL_URL, AMI_RELEASES_URL, OPEN_BASE_URL,
  ESTIMATE_SCORE_API_URL, ERROR_ESTIMATED_FIX_RATE, WARNING_ESTIMATED_FIX_RATE
- skill-prompt.ts utility

CLI bundle reduced by ~39KB (324KB → 285KB).

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>

* chore: remove .ami directory and all remaining Ami references

- Delete .ami/ directory (42 skill/config files)
- Delete packages/website/src/app/open/ (Ami redirect page)
- Remove Ami references from README.md, share page, install-skill
  route, llms.txt, and install-skill.sh
- Zero Ami references remain in the codebase

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>

* chore: move .ami/skills to .agents/skills

Relocates all skill files from the deleted .ami/ directory into
.agents/skills/ and removes .agents from .gitignore so they are
tracked in version control.

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>

* fix: recognize custom text components in rn-no-raw-text rule

Fixes #93: The rn-no-raw-text rule now recognizes a much wider set of
text-handling components:

1. Expanded built-in names: Typography, Paragraph, Span, H1-H6
2. Keyword matching instead of suffix-only: any component whose name
   contains Text, Title, Label, Heading, Caption, Subtitle, Typography,
   Paragraph, Description, or Body is treated as text-handling
   (e.g. StyledText, BodyText, CustomLabel, MyTypography)
3. New textComponents config option: users can specify arbitrary
   component names in react-doctor.config.json that should be treated
   as text-handling:

   {
     "textComponents": ["MyCustomComp", "NativeWindText"]
   }

   Diagnostics inside these components are filtered out post-lint.

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>

* chore: upgrade dependencies

- oxlint: 1.47.0 → 1.59.0 (fixes SIGABRT crashes on large projects #84/#77)
- oxfmt: 0.32.0 → 0.44.0
- typescript: 5.9.3 → 6.0.2
- vitest: 4.0.18 → 4.1.4
- tsdown: 0.20.3 → 0.21.7
- knip: 5.83.1 → 6.3.1

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>

* fix: add file count limit per oxlint batch to prevent OOM crashes

Mitigates #84/#77: Adds OXLINT_MAX_FILES_PER_BATCH (500) to cap the
number of files per oxlint invocation. Combined with the oxlint upgrade
from 1.47 to 1.59, this prevents SIGABRT/OOM crashes on large projects
with 100+ source files.

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>

* feat: add --staged flag for pre-commit hook scanning

Fixes #115: The --staged flag scans only files in the git index
(staged with git add). It materializes staged content to a temp
directory so oxlint reads the exact index version, not the working
tree version. This is critical for pre-commit hooks where the working
tree may differ from what's being committed.

Usage:
  npx react-doctor . --staged --fail-on error

For lint-staged:
  "*.{tsx,jsx}": "npx react-doctor . --staged --fail-on error"

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>

* refactor: code review fixes per AGENTS.md rules

- DRY: Extract shared createFileLinesCache in filter-diagnostics.ts
- DRY: Extract resolveFailOnLevel in cli.ts (was duplicated in
  staged and normal paths)
- DRY: Reuse exported getCalleeName from helpers.ts instead of
  duplicating in state-and-effects.ts and performance.ts
- Magic number: Move maxBuffer 10MB to GIT_SHOW_MAX_BUFFER_BYTES constant
- Variable naming: prevLine → previousLine, tagName → fullTagName,
  leafName → leafTagName, configFiles → projectConfigFilenames
- Remove .pop()! non-null assertion in favor of .at(-1) with fallback

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>

* fix: prevent shell injection in staged file content reading

Replace execSync with string interpolation with spawnSync using an
args array. File paths with shell metacharacters (quotes, semicolons,
etc.) in their names are now passed safely without shell interpretation.

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>

* fix: pass resolved config to staged scan to preserve ancestor inheritance

The staged scan materializes files to /tmp, where loadConfig's ancestor
traversal would never find a monorepo root's config. Now the CLI resolves
config from the real project directory first and passes it via the new
configOverride ScanOption, so customRulesOnly, share, ignore rules,
and textComponents all apply correctly during --staged scans.

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
2026-04-10 21:49:25 -07:00
Aiden Bai 66e1318b0e 0.0.31 react-doctor@0.0.32 2026-04-06 16:27:49 -07:00
Aiden Bai 1aef45a509 fix: resolve 7 GitHub issues (catalog resolution, file ignoring, CLI, RN, Next.js, offline, monorepo) (#108)
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
react-doctor@0.0.31
2026-04-06 16:24:32 -07:00
Akshay Ashok 720f42192b fix: apply ignore.files as pre-filter to skip linting ignored files (#110) 2026-03-30 02:14:58 -07:00
Aiden Bai ec4a41fd82 0.0.30 2026-03-06 21:44:44 -08:00
Aiden Bai c405f4a378 fix: resolve multiple GitHub issues (#71, #72, #76, #77, #83, #84, #86, #87, #89, #92, #93, #94)
- Bun workspace catalog: version resolution (#87)
- Leading / in glob ignore patterns (#86)
- Inline suppression comments (#72)
- Offline score calculation fallback (#89)
- Suppress share link when offline (#92)
- Actionable oxlint crash messages (#84, #77)
- EISDIR/EACCES crash in readPackageJson (#71)
- Custom React Native text components (#93)
- Misleading redirect rule message (#83)
- @expo/vector-icons deprecation update (#76)
- MotionConfig/reducedMotion detection (#94)
- Extract shared isPlainObject util, remove unnecessary casts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 21:26:52 -08:00
Aiden Bai a8604b97a4 fix 2026-03-06 20:36:59 -08:00
Aiden Bai 97b21f18ee refactor: replace fs.existsSync with isFile utility for improved file checks
- Updated multiple utility files to use isFile for checking file existence instead of fs.existsSync.
- This change enhances code readability and consistency across the project.
- Adjusted related tests to ensure compatibility with the new file-checking method.
2026-02-21 01:21:44 -08:00
Aiden Bai 2b46f99b50 new ver 2026-02-20 19:46:09 -08:00
Aiden Bai 1947859c7a fix (#68) 2026-02-20 19:16:18 -08:00
Aiden Bai 3920190de1 fix 2026-02-20 18:30:47 -08:00
Aiden Bai 332747d664 fmt 2026-02-20 18:26:57 -08:00
Aiden Bai 7b885c0c55 fmt 2026-02-20 17:36:19 -08:00
Aiden Bai dde651118a fmt v0.2.1 2026-02-20 17:36:11 -08:00
Igor Bedesqui 9f51b9d9f7 feat(action): add diff input and optional PR comment posting (#63)
- add `diff` input to pass `--diff <base>` to the CLI
- add `github-token` input for posting findings as PR comments
- stale comment cleanup: deletes previous react-doctor comments before posting
- tee output to file only when github-token is set (no change for existing users)
- add GitHub Actions section to README with input reference table

closes millionco/react-doctor#61

Amp-Thread-ID: https://ampcode.com/threads/T-019c7ad9-c09d-713d-a08d-c699637a6846

Co-authored-by: Amp <amp@ampcode.com>
v0.2.0
2026-02-20 17:16:42 -08:00
Athul Binu 79af7cae7f feat: add Expo and React Native framework detection and enable RN-specific rules (#64)
React Native/Expo projects were detected as "unknown" framework, and the 8
existing rn-* lint rules were never enabled in the oxlint config. This change:

- Adds "expo" and "react-native" as recognized Framework types
- Detects expo/react-native packages in dependency detection
- Conditionally enables all 8 rn-* rules when framework is Expo or React Native
- Adds "React Native" category mapping and help text for all rn-* rules
2026-02-20 17:14:53 -08:00
Aiden Bai 618350ffe5 razzzzzzzz 2026-02-19 05:55:09 -08:00
Aiden Bai e3db77b993 0.0.28 2026-02-19 05:45:56 -08:00