Commit Graph

38 Commits

Author SHA1 Message Date
Aiden Bai 48ec9a8020 fix(cli): keep large workspace scans responsive (#1549)
* fix: keep large workspace scans responsive

* fix: harden large workspace scans

* test: normalize workspace path on Windows

* fix: resolve workspace exclusions after root redirects

* fix: honor workspace scan boundaries

* fix: keep bounded workspace scans responsive

* fix: preserve dead-code reachability

* refactor: tighten workspace scan controls

* fix(cli): bound staged workspace scans

* fix: eliminate pathological large-file scans

* fix: handle incomplete and conflicted scans

* test: configure conflict merge identity

* perf: cache repeated module resolution probes

* fix(cli): show all incomplete scan warnings

* fix(core): preserve cooperative scan semantics

* perf(cli): streamline workspace scan startup

* perf(cli): harden large workspace scans

* fix(cli): keep scan progress inline

* fix(scan): correct workspace result completeness

* fix(cli): simplify live scan status

* fix(core): make scan deadlines opt-in

* fix(scan): preserve complete workspace results

* perf(scan): accelerate very large workspaces

* fix(scan): yield CPU to foreground work

* perf(scan): avoid unnecessary project indexing

* fix(cli): keep final report inline

* test(cli): stabilize terminal recording setup

* fix(core): honor scan deadline during index probe

* fix(core): skip lint after incomplete index probe

* fix(core): preserve scoped lint ownership

* fix(cli): restore project picker on cancellation
2026-08-03 04:59:45 -07:00
Aiden Bai 9bb44e7462 docs: add ReactBench FP/FN audit skill 2026-08-02 04:35:35 +00:00
Aiden Bai 075865fc25 feat(evals): batch parity treatments in matrix (#1537)
* feat(evals): batch parity treatments in matrix

* fix(evals): close matrix evidence trust gaps

* fix(evals): bind matrix comparison evidence

* fix(parity): validate matrix artifact contents

* fix(parity): bundle matrix corpus evidence

* fix(parity): require lowercase matrix commits

* fix(evals): bound Daytona control-plane cleanup

* fix(evals): close matrix artifact review gaps

* fix(evals): align matrix verifier project hashing

* fix(evals): schedule matrix treatments together

* fix(evals): finalize matrix artifacts safely

* fix(evals): clean partial base publication

* fix(evals): preserve matrix retry batching
2026-07-31 06:44:08 -07:00
Aiden Bai f023fb9aab feat(evals): pair parity scans in one sandbox (#1536)
* feat(evals): pair parity scans in one sandbox

* test(evals): compare paired git directories portably

* test(evals): verify shared git directory semantically

* fix(evals): commit paired output atomically
2026-07-31 03:47:31 -07:00
Aiden Bai 7bbc7561fd feat(evals): add incremental parity caching (#1535)
* feat(evals): add incremental parity caching

* fix(parity): fail closed on uncertain scopes
2026-07-31 03:05:12 -07:00
Aiden Bai 2db2a97283 perf: reduce scan startup and workspace contention (#1533) 2026-07-30 19:42:52 -07:00
Aiden Bai 8c4959bb74 fix(rules): harden parity diagnostics (#1419)
* fix(rules): harden parity diagnostics

* fix(evals): keep parity artifacts parseable

* fix(evals): support Windows harness tests

* fix(fuzz): parse CRLF corpus directives
2026-07-20 22:28:25 -07:00
Aiden Bai 0b0b5ac523 feat(rules): add deterministic UI, accessibility, and animation diagnostics (#1337)
* feat(rules): add 10 motion, accessibility, and design lint rules

Derived from a cross-resource design reference (every principle from the
design knowledge base), deduped against the full existing rule inventory.

Motion: extend no-transition-all to the Tailwind `transition-all` class
(was inline-only) and add no-tailwind-layout-transition for arbitrary
`transition-[<layout-prop>]`.

Accessibility: no-autoplay-without-muted, no-uninformative-aria-label,
no-target-blank-without-rel, and no-low-contrast-inline-style (computes the
real WCAG 2.1 ratio from co-located inline color + backgroundColor).

Design/Tailwind hygiene: no-redundant-display-class, prefer-truncate-shorthand,
no-full-viewport-width, and no-svg-currentcolor-with-fill-class.

All register via codegen (388 rules); 77 co-located tests pass; typecheck,
lint, and format clean.

* fix(rules): correct two false positives found in rule-validate

- no-low-contrast-inline-style: when the inline style has no `fontSize`,
  the text may be sized large via a class (`text-5xl`), so fall back to the
  lenient 3:1 large-text threshold instead of 4.5:1 to avoid flagging large
  text that only needs 3:1. Stricter 4.5:1 applies only when the size is a
  visible normal size.
- no-redundant-display-class: `<li>` defaults to `display: list-item`, not
  `block`, so `block` on an `<li>` is meaningful — removed `li` from the
  block-default set.

Adds regression tests for both.

* fix(rules): resolve Cursor Bugbot findings

- no-low-contrast-inline-style: use a solid `background` shorthand color as
  the background (was skipped entirely, missing low-contrast pairs); skip only
  gradients/images/ambiguous (both backgroundColor + background). Also treat a
  string `fontWeight: "700"` as bold for the large-text threshold.
- no-transition-all: match `transition-all` as a whole Tailwind token (segment
  after variant prefixes) so compound classes like `transition-all-custom` are
  not falsely flagged.
- no-svg-currentcolor-with-fill-class: exclude stroke-WIDTH utilities
  (`stroke-2`, `stroke-[1.5]`) — they set thickness, not color — so they no
  longer false-conflict with `stroke="currentColor"`.

Adds regression tests for each.

* feat(rules): add 3 Tailwind-canonicalization rules from ui.sh guidance

- no-deprecated-tailwind-class: flag Tailwind v4 renames (bg-gradient-* →
  bg-linear-*, flex-shrink-* → shrink-*, flex-grow-* → grow-*,
  overflow-ellipsis → text-ellipsis). Gated on a new `tailwind:4` capability.
- no-arbitrary-px-font-size: `text-[13px]` → rem so text scales with the
  user's root font size (px stays fine for border-*/outline-*).
- prefer-dvh-over-vh: `h-screen`/`min-h-screen`/`h-[100vh]` → dvh (tailwind:3.4).

Adds `tailwind:4` capability to @react-doctor/core and extracts a shared
`get-class-name-tokens` util (variant-aware token splitting), reused by
no-transition-all and no-svg-currentcolor-with-fill-class.

* fix(rules): adversarial-review false positives in the new design rules

- no-tailwind-layout-transition: match transition-[…] property names exactly
  (comma-split + set) so SVG `stroke-width` / `border-width` — which merely
  contain "width" — are no longer flagged as HTML layout thrash.
- no-full-viewport-width: drop `max-w-*` / `maxWidth` (a defensive cap, not the
  overflow footgun); keep `w-screen`/`w-[100vw]`/`width`/`minWidth`.
- prefer-dvh-over-vh: drop `max-h-*` / `maxHeight` (a valid height cap, e.g. a
  scrollable modal); keep `h-screen`/`min-h-screen`/`height`/`minHeight`.
- no-deprecated-tailwind-class: only rename `bg-gradient-to-*` → `bg-linear-to-*`
  (v4 radial/conic are `bg-radial`/`bg-conic`, not `bg-linear-radial`).

Adds regression tests for each false positive.

* fix(rules): resolve thermo-review false positives

- no-svg-currentcolor-with-fill-class: only treat UNPREFIXED `fill-*`/`stroke-*`
  color classes as conflicts; `hover:fill-blue-600` / `dark:fill-white` (state-
  gated, no static conflict with the base `fill="currentColor"`) are no longer
  flagged — a very common icon pattern.
- no-low-contrast-inline-style: reject 4-arg `rgb(0,0,0,0.5)` / slash-form alpha
  (was judged opaque), and bail when the style object contains a `{...spread}`
  that could override the colors. Tag `test-noise` to match sibling no-tiny-text.
- no-full-viewport-width: drop the redundant `category: "Architecture"` override
  (it's the design-bucket default).
- Extract `has-jsx-spread-attribute` util; use it in the two a11y rules.

Adds regression tests for the variant-prefixed svg classes, rgb-alpha, opaque
rgb, and the style-spread bail.

* refactor(rules): deslop the design/motion/a11y rule additions

Consolidate duplication surfaced by an adversarial deslop pass; no behavior
change (the same diagnostics fire for the same inputs):

- no-autoplay-without-muted: fold isStaticallyTrue/isStaticallyFalse into one
  tri-state resolveStaticBoolean, mirroring the existing parseJsxValue idiom
- no-svg-currentcolor-with-fill-class: collapse the duplicated fill/stroke
  branches into a single loop over the two paint attributes
- no-low-contrast-inline-style: hoist the repeated `properties` fallback
- no-uncontrolled-input: drop the local hasJsxSpreadAttribute copy in favor of
  the branch's new shared utils/has-jsx-spread-attribute, completing the extraction

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(rules): no-deprecated-tailwind-class stops flagging overflow-clip

`overflow-clip` is a current Tailwind utility for `overflow: clip` (v3.1+,
still present in v4) — it was never renamed to `text-clip`, which sets the
unrelated `text-overflow: clip`. The stray mapping produced an incorrect
"renamed in v4" suggestion (Cursor Bugbot). Only `overflow-ellipsis ->
text-ellipsis` is a real rename; keep that. Adds a regression test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(capabilities): require a confirmed Tailwind major for the tailwind:4 gate

`tailwind:4` gates no-deprecated-tailwind-class, which tells users a class was
renamed/removed in v4. The optimistic-on-null policy (assume latest for an
unparseable spec like `workspace:*`) is right for the suggestion rule behind
`tailwind:3.4`, but for a deprecation rule it surfaces confidently-wrong
"renamed in v4" warnings on a v3 project. Require a CONFIRMED parsed major >= 4
for tailwind:4 — favor a false negative over a false positive (Cursor Bugbot).
`tailwind:3.4` stays optimistic. Adds an unparseable-version regression test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(rules): resolve Bugbot/Devin review findings on design/a11y rules

- no-full-viewport-width: match variant-prefixed `md:w-screen` / `lg:min-w-screen`, mirroring the sibling `prefer-dvh-over-vh` regex (overflow happens at every breakpoint).
- no-target-blank-without-rel: resolve `rel={'noopener'}` expression-container literals instead of treating them as dynamic, so reverse-tabnabbing isn't silently missed; share one literal resolver with the `target` check.
- Extract the bare `16` root-font-size divisor into `ROOT_FONT_SIZE_PX` per the magic-number convention.

* fix(rules): no-uninformative-aria-label resolves braced string literals

Extracts no-target-blank-without-rel's inline getStringLiteralAttributeValue
into a shared util and uses it in no-uninformative-aria-label, so a static
braced literal like aria-label={'icon'} is judged instead of silently skipped
(getJsxPropStringValue only handles plain string literals). Adds regression
tests for the braced uninformative and braced descriptive cases.

Addresses Bugbot review on #850.

* test(rules): add liveness fixtures after rebase

* fix(rules): address automated review findings

* fix(rules): handle leading-dot stroke widths

* fix(rules): skip dynamic background overrides

* fix(rules): address target blank and transition reviews

* fix(rules): skip svg layout transitions

* fix(rules): preserve font line height suggestion

* fix(rules): match complete transition utilities

* fix(rules): share layout transition properties

* fix(rules): bound deprecated utility prefixes

* fix(rules): clear dynamically overridden colors

* fix(rules): tighten style and svg precision

* fix(rules): preserve competing display utilities

* fix(rules): preserve animation property scopes

* feat(rules): expand deterministic design checks

* feat(rules): add structural design diagnostics

* feat(rules): complete source-auditable design checks

* fix(rules): skip unresolved input types

* fix(rules): tighten layout and boundary detection

* fix(rules): close motion and padding gaps

* fix(rules): tighten surface and line-height proofs

* fix(rules): evaluate opaque hsl contrast

* fix(rules): honor effective static values

* fix(rules): honor final inline style declarations

* fix(rules): respect variants and share surface checks

* fix(rules): traverse fragments and preserve variant scope

* feat(rules): deepen UI quality diagnostics

* feat(rules): add deterministic animation diagnostics

* fix(rules): retain in-house accessibility checks

* feat(rules): expand deterministic UI diagnostics

* feat(rules): detect generated interface patterns

* fix(rules): respect dynamic style overrides

* feat(rules): expand deterministic design diagnostics

* fix(rules): close design precision gaps

* feat(rules): expand deterministic design and accessibility checks

* feat(rules): detect unsafe Motion API usage

* test(fuzz): remove duplicate module snippets

* feat(cli): add focused design command

* fix(design): include the complete rule bucket

* fix(core): align opt-in rule gates

* feat(rules): expand deterministic motion and interaction checks

* feat(rules): harden deterministic design diagnostics

* fix(rules): harden design expansion diagnostics

* fix(rules): apply large-corpus precision review

* fix(rules): suppress large-corpus accessibility noise

* fix(core): detect compiler in rstack configs

* fix(a11y): avoid responsive navigation false positives

* fix(rules): reduce design corpus false positives

* fix(rules): restore will-change build after rebase

* fix(rules): harden all-rules corpus precision

---------

Co-authored-by: Rayhan Noufal Arayilakath <me@rayhanadev.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 01:09:01 -07:00
Aiden Bai 7c9dbeda0d feat(evals): add Daytona corpus parity runner (#1384) 2026-07-19 00:21:46 -07:00
Aiden Bai 2ba83c3201 fix(rules): recognize callback ref convergence (#1335)
* fix(rules): recognize callback ref convergence

* fix(rules): tighten did-update transition guards

* fix(rules): invalidate reassigned callback refs

* fix(rules): reject disjunctive convergence guards

* fix(rules): follow logical callback refs

* fix(rules): respect logical ref selection

* fix: make did-update guards branch-aware

* fix(rules): resolve destructured previous props

* fix(rules): ignore static ref handler names

* fix(rules): handle private callback refs

* fix(rules): distinguish private member identities

* fix(rules): cover callback ref guard edges

* fix(rules): preserve branch-sensitive convergence

* fix(rules): invalidate overwritten callback refs

* fix(rules): track callback ref writes in IIFEs
2026-07-17 06:10:47 -07:00
Ray Arayilakath c1916d577d fix(effect-needs-cleanup): require exhaustive loop cleanup (follow-up to #1199) (#1212)
* refactor(effect-needs-cleanup): simplify loop cleanup review deslop

- doesCleanupFunctionReleaseUsage: hoist the duplicated ChainExpression unwrap into one binding and short-circuit async/generator checks before helper recursion
- resolveIteratorCollectionKey: collapse the split symbol/kind guards back into one
- isAssignmentFormForOfIteratorReference: consolidate the two near-duplicate walkAst reference-matching blocks into one assignsReferencedSymbol closure

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(effect-needs-cleanup): address review findings on fix/effect-cleanup-loop-pairs

- effect-needs-cleanup.ts:1692 — require an exhaustive removal loop in doesReleaseCallMatchUsage's for-of pairing so retained-handler analysis (fileContainsReleaseForUsage) no longer treats a break-truncated loop as a full release; pinned both directions plus the effect-cleanup path in effect-needs-cleanup.regressions.test.ts
- utils/resolve-event-listener-capture.ts — folded the rule-local resolveStaticEventListenerCapture fork into the shared resolveEventListenerCapture behind allowComputedString/allowIndeterminateEntries options; both consuming rules keep their exact prior semantics
- packages/fuzz/corpus — relocated the true-positive fixture effect-needs-cleanup--assignment-form-loop-mismatch.tsx out of the confirmed-false-positive regressions/ directory into new true-positives/, and documented (README, smoke test, fuzz skill) that firing expectations live in the rule's unit suite, not the fuzz harness

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore(changeset): document effect-needs-cleanup loop cleanup fix

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 05:56:39 -05:00
Aiden Bai 9b59d96f06 feat(fuzz): verdict-preserving mutation oracle + two new rules + detection robustness sweep
Fuzz: add the verdict-drop oracle — semantics-preserving source rewrites
(parenthesized/cast/non-null receivers, concise-arrow-to-block returns,
no-op prologues, optional-chain and computed-member spellings) applied to
firing programs; a rule that goes silent under one keys on incidental
token shape. Wired into fuzz-rule as an advisory finding kind, a strict
per-rule robustness suite over the audited liveness fixtures, a
registry-wide census script, and new regression-corpus entries.

Rules: add no-locale-format-in-render (locale/timezone-dependent
formatting during render in SSR-capable projects → hydration mismatch,
gated off client-only frameworks) and extend
prefer-use-sync-external-store to detect hand-rolled module-scope stores
(let + listener set + subscribe fn consumed via useState snapshot +
useEffect subscription).

Robustness sweep: shared is-no-op-statement / is-global-method-call /
executes-during-render utilities and cast-wrapper unwrapping across the
rule registry so detection survives the rewrites above; census is 0
drops across all fixture-covered rules.
2026-07-09 03:37:39 -07:00
devin-ai-integration[bot] 0748a4ba19 test: adversarial fuzzing harness for every rule (@react-doctor/fuzz) (#1022)
Co-authored-by: Aiden Bai <aiden.bai05@gmail.com>
2026-07-02 15:09:02 -07:00
Ray Arayilakath eafac9d4f1 fix(cli): recommend --scope changed instead of deprecated --diff (#834) (#844)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 18:45:02 -04:00
Aiden Bai 451beeb284 feat(rules): add 3 mined platform/correctness rules (batch 2) (#828)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Rayhan Noufal Arayilakath <me@rayhanadev.com>
2026-06-16 00:06:34 -04:00
Ray Arayilakath 0a7edbd3d3 chore(skills): add rde-eval runbook for testing rules against the corpus (#836)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 21:07:46 -04:00
Ray Arayilakath 2f26228e36 feat: consolidate scan scope into one --scope flag (#769)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-10 02:51:42 -04:00
Ray Arayilakath 571ca9c802 feat(skills): add product-thinking runbook for public-surface changes (#748)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 16:30:21 -04:00
Aiden Bai 25cc69bd8f refactor(skill): fold doctor-explain into react-doctor references/explain.md (#729) 2026-06-07 20:39:56 -07:00
Aiden Bai ca5ea4c61e fix 2026-06-06 23:33:47 -07:00
Aiden Bai a11faa2999 fix 2026-06-06 23:25:44 -07:00
Ray Arayilakath f375dbb24f chore(skills): add /ship release pipeline skill (#651)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 15:47:18 -04:00
Ray Arayilakath 9777f1ac45 feat(cli): add rules management commands and doctor-explain skill (#617)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 05:55:47 -04:00
Ray Arayilakath f1913f27f5 chore(skills): add truffler symbol-search tooling and deslop skill (#618)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-31 21:45:11 -04:00
Ray Arayilakath 1c87c65a87 docs: mention changesets in rule validation (#571)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-28 23:14:22 -04:00
Aiden Bai dc5f2f0972 feat(rn): add rn-no-renderitem-key rule (#526) 2026-05-27 14:40:01 -07:00
Ray Arayilakath 8244f1f28f docs: consolidate fragmented rule skills (#522)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 04:46:10 -04:00
Ray Arayilakath 444fbeb760 feat: add skills and docs for writing new rules (#495)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 02:41:23 -04:00
Nisarg Patel 8b994b9ac0 chore(skill): bump version to 1.1.1 and refine playbook description
Updated the version of the react-doctor skill from 1.1.0 to 1.1.1. The playbook description has been refined for clarity, emphasizing its role as the single source of truth in the triage process. No functional changes were made to the skill's operation.
2026-05-25 22:54:14 -07:00
Nisarg Patel 61ea050907 feat(skill): add /doctor full-triage workflow that fetches the canonical playbook
The skill body now points the agent at https://www.react.doctor/prompts/react-doctor-agent.md
when the user types `/doctor` or asks for a full triage. The remote playbook is the
single source of truth (same scan -> filter -> triage -> fix -> validate loop as the
daily GitHub Actions bot, adapted for local use), so prompt edits propagate to every
installed agent on the next invocation without re-publishing the skill.

Bumps SKILL.md version 1.0.0 -> 1.1.0.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 22:38:06 -07:00
Aiden Bai f04a2f406a install react doctor agent hook 2026-05-24 19:24:05 -07:00
Aiden Bai e642d461cc fix 2026-05-24 16:20:47 -07:00
Aiden Bai cd46ac1810 chore(docs): prune accreted docs, fix stale refs from #307 + #440 (#447)
README (594 → 394 lines, -34%):
- collapse 3 redundant YAML examples in `PR blocking` to one explanation
- shrink Husky/lint-staged walkthrough to one config block + one warning
- compress `Custom rules` from 60 lines of numbered tutorial to a single example
- merge `Agent and CI integration` bullet list (duplicated CLI ref + PR blocking + Node API)
- collapse 5 prose paragraphs after the config table into a 5-bullet list
- cut implementation-detail bullet list of `rn-no-raw-text` Platform.OS forms
- drop NickvanDyke "we ported these rules" trivia paragraph
- replace duplicated install block in `Resources & Contributing Back` with one line
- inline single-row `Optional companion plugins` table

CONTEXT.md + .specs/inspect-pipeline.md:
- drop `actions/review` / `@react-doctor/review-action` section (reverted in #443)
- replace `@react-doctor/project-info` / `@react-doctor/types` refs with
  `core/src/project-info` / `core/src/types` (folded into core in #440)
- fix "13 leaf reasons today" miscount → 12
- update service count 9 → 11

Deletes:
- TODOS.md (442-line working backlog — belongs in issues, not the repo)
- .agents/skills/thermo-nuclear-code-quality-review/ (200-line skill)
- packages/react-doctor/CHANGELOG.md.bak (68KB tracked backup file)

Validated: `pnpm format`, `pnpm lint`, `pnpm typecheck`, `pnpm test` (1449 pass, 3 skip).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 01:42:38 -07:00
Aiden Bai 5759fd4699 feat(skills): add thermo-nuclear-code-quality-review skill (#436) 2026-05-22 19:59:14 -07:00
Aiden Bai f0198e2f2d refactor: extract @react-doctor/{types,project-info,core} internal packages + cleanup (#249) 2026-05-14 22:22:19 -07:00
Aiden Bai 4d622970b5 Revert "pub"
This reverts commit 4d9d1a5fcf.
2026-05-14 22:05:22 -07:00
Aiden Bai 4d9d1a5fcf pub 2026-05-14 22:03:02 -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