* fix: classify fragment returns of translation text as text-producing
Fragment returns mixing translation text elements (fbt/fbs) with literal
text now classify correctly as text-producing components. The fragment
is render-transparent, so both halves land in the caller's <Text>.
Fixes#1731
- Extract isTranslationTextJsxRoot helper that recursively checks
fragments
- Fragment children that are all translation elements (plus literals)
pass the check
- Fragment children with non-text elements still fail (guard test added)
- Add regression coverage for the reported mixed-literal case
Co-authored-by: Skosh <skoshx@users.noreply.github.com>
* chore: add changeset for fragment translation text fix
Co-authored-by: Skosh <skoshx@users.noreply.github.com>
* fix: keep fragment translation classification conservative
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Skosh <skoshx@users.noreply.github.com>
Co-authored-by: Aiden Bai <aiden@million.dev>
* fix: prevent false positive in js-set-map-lookups for String().includes()
- Add check to ensure String() refers to global constructor, not shadowed variable
- Add regression tests for the String() constructor case
- Add corpus regression test file
Closes#1733
Co-authored-by: Skosh <skoshx@users.noreply.github.com>
* fix: recognize global String substring lookups
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Skosh <skoshx@users.noreply.github.com>
Co-authored-by: Aiden Bai <aiden@million.dev>
* fix(rn-no-raw-text): exempt fbt children of text-wrapping custom components
The rule now checks auto-detected and imported text wrappers when
evaluating transparent wrapper boundaries, not just built-in text
components. This fixes a false positive where `<fbt>` (a compile-time
translation tag) inside a custom component that renders `<Text>` was
incorrectly reported as a crash.
Closes#1722
Co-authored-by: Skosh <skoshx@users.noreply.github.com>
* chore: add changeset for #1722
Co-authored-by: Skosh <skoshx@users.noreply.github.com>
* fix(rn-no-raw-text): verify fbt text wrappers
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Skosh <skoshx@users.noreply.github.com>
Co-authored-by: Aiden Bai <aiden@million.dev>
* fix: remove @expo/metro-config from redundant dependency check
The rule incorrectly flagged @expo/metro-config as redundant, suggesting
to use expo/metro-config instead. While expo does re-export the main entry
point, it does NOT re-export subpaths like babel-transformer.
Projects that import @expo/metro-config/babel-transformer (documented by
Expo for extending the Babel transformer) cannot use expo/metro-config/
babel-transformer as it does not exist. The suggested fix is impossible
to follow for subpath imports.
Since the static check cannot distinguish between main entry and subpath
usage, and Expo SDK upgrades keep both packages in lockstep, the version
drift risk is minimal. Removing this entry prevents false positives.
Closes#1726
Co-authored-by: Skosh <skoshx@users.noreply.github.com>
* chore: add changeset for expo/metro-config fix
Co-authored-by: Skosh <skoshx@users.noreply.github.com>
* fix: keep required Expo Metro config subpaths
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Skosh <skoshx@users.noreply.github.com>
Co-authored-by: Aiden Bai <aiden@million.dev>
* fix(ci): harden workflows against cache poisoning and document unsigned tags
- Restrict TURBO_TOKEN/TURBO_TEAM to main-branch pushes only across
ci.yml, code-quality.yml, and react-doctor.yml. First-party PR branches
no longer have cache write access (preventing cache poisoning attacks).
PRs run with a cold cache.
- Add SECURITY comments in action-version-bump.yml explaining that tags
are unsigned and recommending SHA pinning for hardened CI.
- Update install-github-workflow.ts template to include security note
about @v2 floating major tag and SHA pinning recommendation.
Closes#1685
Co-authored-by: Skosh <skoshx@users.noreply.github.com>
* fix(ci): make pull request cache read-only
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Skosh <skoshx@users.noreply.github.com>
Co-authored-by: Aiden Bai <aiden@million.dev>
* fix(supply-chain): remove oxlint/oxc from minimumReleaseAge exemption
Remove oxlint/oxc packages from minimumReleaseAgeExclude to restore
the 2-hour release quarantine for these third-party dependencies.
Oxlint/oxc is the core analysis engine of react-doctor - the exact
dependency where a compromised or bad release has maximal impact.
The exclusion was removing the one guard that protects that path.
Keep only agent-install in the exclusion list, as it's documented
as a first-party package.
Closes#1680
Co-authored-by: Skosh <skoshx@users.noreply.github.com>
* chore: add changeset for oxlint release age fix
Co-authored-by: Skosh <skoshx@users.noreply.github.com>
* chore(deps): keep release-age policy internal
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Skosh <skoshx@users.noreply.github.com>
Co-authored-by: Aiden Bai <aiden@million.dev>
* test: add comprehensive tests for barrel file directory imports
- Add tests for tsconfig path alias resolution to index.ts
- Add tests for baseUrl resolution to index.ts
- Add tests for relative directory imports to index.ts
- Add Next.js-specific test with auto-detected entries
- Add direct resolver tests to verify directory resolution
Tests confirm that barrel files imported via directory paths (e.g., @/features/foo -> foo/index.ts) are correctly resolved and not flagged as unused.
Related to issue #1699
Co-authored-by: Skosh <skoshx@users.noreply.github.com>
* test: add moduleResolution bundler test for directory imports
Test confirms that directory imports work correctly with moduleResolution: bundler setting (used by Next.js).
Co-authored-by: Skosh <skoshx@users.noreply.github.com>
* test(core): cover directory barrel reachability
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Skosh <skoshx@users.noreply.github.com>
Co-authored-by: Aiden Bai <aiden@million.dev>
* fix: check root directory for CI config instead of project count
Multi-project scans were unconditionally recommending GitHub Actions
even when already existed at the
repository root. The logic checked individual project directories for
the workflow, which would fail in monorepos where the workflow lives
at the root.
Fixed by checking whether CI is configured at the root directory
(where the scan is running) instead of:
1. Unconditionally for multi-project scans (projectScans.length > 1)
2. Per-project directory checks that miss root-level workflows
Closes#1696
Co-authored-by: Skosh <skoshx@users.noreply.github.com>
* chore: add changeset for CI recommendation fix
Co-authored-by: Skosh <skoshx@users.noreply.github.com>
* chore: format code
Co-authored-by: Skosh <skoshx@users.noreply.github.com>
* fix(cli): measure CI recommendations
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Skosh <skoshx@users.noreply.github.com>
Co-authored-by: Aiden Bai <aiden@million.dev>
* fix(rerender-state-only-in-handlers): recognize state consumed via member expression hook calls
State consumed by hook calls through member expressions (e.g. styles.useVariants({ state }))
was incorrectly flagged as unused. This commonly occurs with react-native-unistyles,
where state selects style variants via StyleSheet.useVariants().
The rule now checks both direct identifier calls (useX) and member expression calls
(obj.useX) when collecting custom hook arguments, following the existing pattern used
throughout the codebase.
Fixes#1716
Co-authored-by: Skosh <skoshx@users.noreply.github.com>
* chore: add changeset for member expression hook fix
Co-authored-by: Skosh <skoshx@users.noreply.github.com>
* test: add edge case for non-hook member expressions
Co-authored-by: Skosh <skoshx@users.noreply.github.com>
* fix(rules): preserve state setter bindings
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Skosh <skoshx@users.noreply.github.com>
Co-authored-by: Aiden Bai <aiden@million.dev>
* fix: ignore TypeScript type-only positions in no-unguarded-browser-global-at-module-scope
The rule was incorrectly flagging browser-global names (window, navigator, etc.) when they appeared as property keys in TypeScript interfaces and type aliases. These are type-only positions that TypeScript erases during compilation, so no runtime reference error occurs.
Add isTypeScriptTypePosition check to skip identifiers in TS type contexts before reporting. Add regression tests covering interface properties, type alias properties, and the preserved behavior for actual runtime references.
Fixes#1667
Co-authored-by: Skosh <skoshx@users.noreply.github.com>
* chore: add changeset for #1667 fix
Co-authored-by: Skosh <skoshx@users.noreply.github.com>
* test: add edge case tests for TypeScript type positions
Co-authored-by: Skosh <skoshx@users.noreply.github.com>
* test: preserve type-only browser global regression
* docs(changeset): clarify type-only global fix
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Skosh <skoshx@users.noreply.github.com>
Co-authored-by: Aiden Bai <aiden.bai05@gmail.com>
* fix: extract entry points from implicit sub-projects outside workspace patterns
When the root package.json declares workspaces, sub-projects outside the
declared workspace globs were discovered but their entry points (main, bin,
exports) were skipped. This caused false positives in unused-file detection.
The fix removes the isDeclaredWorkspace predicate from entry extraction -
any package with a valid package.json should have its entry points extracted
regardless of workspace pattern matching.
Fixes#1665
Co-authored-by: Skosh <skoshx@users.noreply.github.com>
* chore: add changeset for issue #1665 fix
Co-authored-by: Skosh <skoshx@users.noreply.github.com>
* test: consolidate implicit workspace entry regression
* docs(changeset): clarify implicit entry fix
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Skosh <skoshx@users.noreply.github.com>
Co-authored-by: Aiden Bai <aiden.bai05@gmail.com>
* fix: prevent stack overflow in function resolution with depth limiting
Add depth limiting to resolveExactLocalFunction to prevent infinite
recursion when resolving function references through deeply nested
expressions, TypeScript path aliases, or complex import chains.
The function now enforces a configurable depth limit
(FUNCTION_RESOLUTION_MAX_DEPTH = 15) and returns null when exhausted
rather than continuing to recurse.
- Add FUNCTION_RESOLUTION_MAX_DEPTH constant (15 levels)
- Thread remainingDepth parameter through all resolution helpers
- Add unit tests for depth limiting
- Add regression test for zustand + Next.js + path aliases case
Closes#1657
Co-authored-by: Skosh <skoshx@users.noreply.github.com>
* test: exercise function resolution stack guard
* refactor: share function chain fixture builder
* test: tighten function resolution regressions
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Skosh <skoshx@users.noreply.github.com>
Co-authored-by: Aiden Bai <aiden.bai05@gmail.com>
* feat(cli): record React-aware browser traces
Add an interactive Playwright scanner that streams DevTools traces to disk and reports React rendering hotspots for humans and agents.
* fix(cli): use shared telemetry anonymizer
Keep runtime URL redaction on the canonical core utility after the telemetry helper consolidation.
* feat(cli): visualize component renders during traces
Flash labeled component outlines during runtime scans so users can connect slow interactions to the React work they trigger.
* feat(skill): add runtime performance workflow
Guide agents through repeatable React traces so performance findings stay tied to measured interactions.
* docs(cli): guide runtime scan setup
Explain the recording flow, browser isolation, authenticated sessions, output formats, and trace privacy directly in command help.
* fix(cli): harden runtime trace capture
Preserve evidence across navigations while keeping structured output, telemetry, trace files, and attached-browser cleanup safe under failures and cancellation.
* fix(cli): preserve latest navigation evidence
Keep cross-document layout shifts comparable and retain the most recent bounded performance entries so the recorded interaction remains represented.
* fix(cli): bound runtime scan browser resources
Prevent trace deadlocks, cross-tab capture, and unbounded browser evidence while preserving navigation data through an isolated validated relay.
* fix(cli): preserve busy-page runtime snapshots
Stop tracing concurrently with final probe collection and allow busy pages enough time to flush their bounded performance evidence.
* feat(cli): prompt for runtime scan URL
Let people start an interactive runtime scan without remembering the URL argument while keeping agents and CI deterministic.
* feat(cli): suggest local apps for runtime scans
Replace the bare URL field with a polished chooser that discovers common localhost development servers while preserving custom URL entry.
* fix(cli): use scan terminology in runtime flow
Keep the interactive copy aligned with the command users invoked instead of switching between recording and profiling language.
* fix(cli): center runtime chooser in full terminal
Render the interactive URL chooser as a terminal-sized view so it no longer appears anchored to the previous cursor position.
* fix(cli): harden runtime scan edge cases
Capture popup evidence without leaking browser pages, support redirected interactive output and IPv6 localhost apps, and keep bounded reports honest.