The unref-stdin live probe spawns a child Node that imports the real
.ts source, which only works where Node can natively strip TypeScript
types (>= 22.6, unflagged from 22.18). On the 20.19 CI lane there is no
type-strip path, so the child exited with empty stdout and the test
failed. Guard the suite with describe.skipIf and drop the dead
--experimental-strip-types fallback.
setup-node@v5 defaults `package-manager-cache: true`, which auto-detects
a `packageManager` field (e.g. pnpm) and runs the package manager to
resolve the cache path. Consumer CI that doesn't install pnpm fails with
"Unable to locate executable file: pnpm", which then cascades into the
skipped scan step and a "Missing report path" render crash. The action
runs via `npm exec` and never installs the consumer's deps, so there is
nothing to cache — turn it off.
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(cli): pair verbose diagnostics with per-rule fix-recipe URLs
In --verbose output, every error/warning rule group now prints a
`Fix recipe:` line pointing at the canonical, reviewer-tested prompt
at https://www.react.doctor/prompts/rules/<plugin>/<rule>.md (also
written into the verbose diagnostics dump). This lets the /doctor
playbook fetch the recipe on demand so each fix follows the canonical
recipe instead of being improvised per diagnostic.
Co-authored-by: Cursor <cursoragent@cursor.com>
* chore: drop changeset for verbose fix-recipe URLs
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(cli): make verbose recipe line a fetch directive for agents
Replace the bare "Fix recipe:" label with an explicit
"Fetch & follow the canonical fix recipe before fixing:" directive so
the consuming agent treats the per-rule prompt URL as a step to perform
rather than optional reference docs. Share the line between the verbose
output and the diagnostics dump to keep them in sync.
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(cli): explain per-rule recipe fetching in non-verbose summary
Non-verbose output only lists category counts, so the agent never sees
the per-rule recipe URLs. Add a closing hint that points at the
canonical recipe pattern (https://www.react.doctor/prompts/rules/<plugin>/<rule>.md)
and tells the agent to run --verbose for the exact per-rule URLs, so it
can fetch & follow the recipe before fixing.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): separate recipe hint from per-project summary bars
Add a blank line before the multi-project per-project summary rows so the
recipe-fetch hint isn't visually glued to the project score bars.
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(cli): include per-rule fix recipes in clipboard copy
The "Copy issues to clipboard" summary now prints each rule's canonical
fix-recipe URL (reusing the shared fetch directive) and adds a how-to-fix
step telling the agent to fetch & follow it before fixing, matching the
verbose terminal output.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): show a real example recipe URL in non-verbose summary
The <plugin>/<rule>.md placeholder was ambiguous in non-verbose mode,
where the agent never sees rule names. Use a concrete recipe URL built
from an actual finding (an error when present) as the example, and point
to --verbose for the full per-rule list.
Co-authored-by: Cursor <cursoragent@cursor.com>
* style: format recipe-label constant onto a single line
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): group clipboard issues by plugin/rule for correct recipe URLs
Bugbot: the clipboard summary grouped diagnostics by rule name only, so
two plugins sharing a rule name collapsed into one group and emitted the
first plugin's fix-recipe URL for all of them. Key the grouping by
`<plugin>/<rule>` (matching every other grouping in the codebase) so each
group's recipe URL and header are correct.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(a11y): skip alt-text on Next.js metadata image route files
Next.js App Router treats `opengraph-image.tsx`, `twitter-image.tsx`,
`icon.tsx`, and `apple-icon.tsx` (with optional numeric suffix) as
metadata image routes. Their default export returns an `ImageResponse`
from `next/og` that rasterizes JSX into a static PNG/JPG — no browser
DOM, no screen reader, so `alt` / `aria-*` are unactionable noise.
Extract `isNextjsMetadataImageRouteFilename` as a shared util (so other
DOM-flavoured a11y rules can opt in later) and early-return from
`alt-text.create()` when the filename matches. Adds a regression suite
covering both extensions, numeric suffixes, and the negative case
(`my-opengraph-image.tsx` still flags).
Co-authored-by: Cursor <cursoragent@cursor.com>
* chore(architecture): demote only-export-components to warn
Mixing non-component exports in a component module is a Fast Refresh
hint, not a hard failure — projects routinely co-locate stable
constants alongside components and ship fine. Drop from `error` to
`warn` so triage output reflects the actual severity gradient.
Co-authored-by: Cursor <cursoragent@cursor.com>
* chore(architecture): unregister react-compiler-destructure-method
The React-Compiler memoization premise didn't hold up for the three
canonical hooks the rule targeted: `useRouter`, `useSearchParams`, and
`useNavigation` all return stable references, so destructuring their
methods produces no measurable compiler win. On Pages Router
(`next/router`) destructuring `push` is actively wrong — it captures
a stale reference.
Introduce a `RULE_IDS_TO_SKIP_REGISTRATION` skiplist in the registry
generator (with a required justification comment per entry) so we can
park a rule without deleting its implementation. Drops the rule from
the registered set (297 → 296), `describe.skip`'s the regression
suite, and prunes the rule from the vercel-skill parity table. The
fixture seed is kept so re-enabling is a one-line skiplist edit.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(state-and-effects): promote no-adjust-state-on-prop-change to error
Adjusting derived state inside a useEffect when a prop changes always
forces an extra render with a stale UI between the two commits —
there is no benign instance of this pattern, so a warning understates
the cost. Promote to `severity: "error"` and rewrite the recommendation
and message in the authoritative "what's wrong → why → fix" shape used
by the rest of the error-level effect rules.
Upstream `eslint-plugin-react-you-might-not-need-an-effect` ships this
as a softened `type: "suggestion"`. Document the intentional divergence
(severity + copy only — detector logic is still a 1:1 port) in
`effect/SOURCE.md`, and update the registered-severity assertion in
`scan-resilience.test.ts` to expect `error` for this id.
Co-authored-by: Cursor <cursoragent@cursor.com>
* chore: address review nits — drop explanatory comments, inline regex
Per @rayhanadev's review:
- drop the two `transient-and-async-issues.tsx` block comments around
`useRouter` / `LoginLink` (the test files document the rule's intent
on their own)
- drop the duplicate early-return rationale in `alt-text.ts` (the
shared util's header doc already explains the why)
- inline the metadata-image basename regex into the helper to remove
the named-constant indirection
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(a11y): use context.filename instead of removed getFilename
After merging main, `RuleContext` no longer exposes `getFilename` (PR
\#549 made it `Omit<BaseRuleContext, "getFilename">` and exposed the
absolute path as a property). Switch the alt-text early-return to read
`context.filename` to match the new API.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>