Install
$ agentstack add skill-bensheridanedwards-architectplaybook-react-audit ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
/react-audit
Audit a TypeScript and React project against an opinionated baseline of idiomatic React organised in four layers — hooks correctness, component design, state management, React 18/19 idioms — preceded by a diagnostic snapshot. Then offer to generate an implementation plan for the gaps.
This skill is scoped to React-correctness independent of cost, accessibility, or architecture. Many React concerns live in adjacent audits; the boundary table below is the canonical map.
How this differs from neighbouring audits
The architect-playbook deliberately keeps audit boundaries tight so a single concern lives in a single skill. Every audit in the playbook touches React in some way; this is where the lines fall:
| Concern | Owner | | --- | --- | | Hook correctness (Rules of Hooks, dependency arrays, custom hook design) | /react-audit | | Component-shape patterns (function components, forwardRef, displayName, naming) | /react-audit | | State strategy at the React-API level (when to lift, when to derive, form/server/URL/component split) | /react-audit | | React 18+ primitives usage (useTransition, useId, Suspense, 'use client', server actions, useOptimistic) | /react-audit | | Memoization for runtime cost (performance-driven useMemo/React.memo/virtualization) | /performance-audit | | Memoization for correctness (referential stability that downstream code depends on) | /react-audit | | God components, file size, fan-out as architectural invariants | /architecture-audit | | Single state-management library (Redux vs Zustand vs Jotai chosen once) | /architecture-audit | | jsx-a11y rules and accessibility patterns inside components | /accessibility-audit | | React error boundaries and Suspense + boundary pairing | /error-handling-audit | | ESLint plugin coverage (which React rules are even enabled) | /linting-audit | | Bundle-level concerns: React.lazy configuration, dynamic-import wiring | /bundle-build-audit |
When a single fix passes multiple audits (for example, enabling eslint-plugin-react-hooks satisfies both /linting-audit and /react-audit; choosing a single global state library satisfies /architecture-audit and /react-audit), every relevant audit surfaces the same gap so the user sees it once and resolves it once.
Posture: static-only, no opt-in modes
Hook correctness is best caught by a typed AST analyser, which is exactly what eslint-plugin-react-hooks already is — and /linting-audit already verifies that plugin is configured. Re-running the plugin from this skill would be redundant. The non-lintable parts of this audit (state-strategy patterns, idiom adoption, hook-design quality) are static AST and pattern detection.
If the user wants a fresh real-run capture of hook violations, /linting-audit --with-run is the right tool. This skill stays static.
Usage
/react-audit # default: concise Top 5 + full report saved + ask about plan
/react-audit --worktree # create an isolated Git worktree, then run the audit there
/react-audit --learn # mid-level engineer teaching mode (detailed explanations + file/line examples)
/react-audit --teach # alias for --learn
💡 Pro tip: Add --worktree to run this audit in an isolated Git worktree.
The skill never accepts --apply. The implementation plan is descriptive Markdown.
This audit deliberately has no numeric threshold flags. Most checks are zero-tolerance or qualitative; class-component presence is a partial regardless of count. The canonical path to evolving the baseline itself is /system-self-improve.
The opinionated baseline
A check resolves to one of four statuses:
- present — the invariant holds.
- partial — most signals resolve, with a small number of exceptions, or the codebase shows mixed adherence to a soft check.
- missing — a structural prerequisite is absent (no React installed, for example — the skill stops before this state is ever reached, but the status is reserved for future checks).
- violation — the audit identified concrete code that breaks the invariant.
Layer 0 is informational only and has no status. Layer 4 reports skipped: "react-version-below-18" (and no per-check entries) when React is older than 18.
Layer 0 — Diagnostic snapshot (always written, no pass/fail)
- React version, resolved from
package.jsonand the lockfile (the lockfile is authoritative when it disagrees withpackage.json). - Framework variant (Next.js App Router or Pages Router, Remix, Vite-React, Create React App, plain React).
- Component counts: function components, class components, total. Class components are explicitly counted because their presence shapes layer 2.
- Custom hook count, plus the largest custom hook by line count (helps identify hooks that should be split).
- Detected form library: react-hook-form, formik, native, or none.
- Detected animation library: framer-motion, react-spring, native CSS animation/transitions, or none.
- Detected design system / component library: Radix UI, shadcn/ui, MUI, Chakra UI, Mantine, Ariakit, custom in-house, or none.
- Counts of new-React primitives in source:
useTransition,useDeferredValue,useId,useOptimistic,use(),useFormStatus. 'use client'and'use server'directive counts (App Router projects only; recorded asnullotherwise).forwardRefusage count andReact.memousage count.
Layer 1 — Hooks correctness
| Check | Expectation | Violation signal | | --- | --- | --- | | eslint-plugin-react-hooks recommended set enabled | The hooks plugin's recommended rules are enabled in the active ESLint configuration (or the equivalent Biome rules for projects on Biome). Overlap with /linting-audit; both surface. | Plugin missing, or recommended set off. | | No silenced exhaustive-deps without justification | // eslint-disable-next-line react-hooks/exhaustive-deps lines have a justification comment on the same or adjacent line. Soft check — reported as partial. | Suppressions of exhaustive-deps with no surrounding explanation. | | No empty dependency arrays where dependencies exist | useEffect(() => { ... }, []) is not used when the body legitimately closes over rendered values that change. | Effects with empty dependency arrays that read state, props, or context that varies across renders. | | No data fetching in effects when a query library exists | When TanStack Query, SWR, RTK Query, Apollo Client, or framework-native loaders are available, components fetch via the data layer rather than useEffect plus fetch. (Overlap with /performance-audit's "server state in data layer" check.) | useEffect calling fetch/axios in a project with a query layer installed. | | No state synchronisation | State is derived from props or other state, not stored in useState and re-synchronised by useEffect. | Patterns where a useEffect does setX(propX) to mirror props into state. | | Status enums over multiple booleans | When a component tracks multi-stage state, a single status enum is used ('idle' | 'loading' | 'success' | 'error') rather than separate isLoading, isError, isSuccess booleans that can drift. Soft check — reported as partial. | Three or more boolean state variables that represent a single conceptual lifecycle. | | useRef for mutable non-render state | Mutable values that don't trigger re-renders use useRef, not useState. | useState used to hold a value that is never read in render but mutated frequently. | | Cleanup functions present for side effects | useEffect returns a cleanup for subscriptions, timers, observers, async-cancellation, and event listeners. | Side-effect setup with no matching teardown. | | Memoization for correctness, not just performance | When a callback or value appears in another hook's dependency array and that downstream hook depends on referential stability for correctness (typically a useEffect dependency list), the callback or value is wrapped in useCallback/useMemo. Distinct from /performance-audit's performance-driven memoization: this catches infinite-re-run bugs, not slow renders. | A useEffect whose dependencies include an inline callback created on each render. | | Custom hook design | Custom hooks have a single responsibility, are named useFoo, and return a consistent shape across the codebase (single value for one return, tuple for two, object for three or more). Soft check — reported as partial when adherence is mixed. | Custom hooks returning mixed shapes inconsistently, or named without the use prefix. |
Layer 2 — Component design
| Check | Expectation | Violation signal | | --- | --- | --- | | Function components only in new code | New components are function components; class components persist only as legacy. The audit reports class components as partial rather than violation because legacy migrations are slow. The strongest signal is a class component whose file has been modified more recently than the most recent function-component file — recorded explicitly. | Class components present (count recorded); class component newer than the most recent function component (flagged). | | Components named in PascalCase | Component files and exported component identifiers use PascalCase. | Non-PascalCase component definitions. | | Hooks named with use prefix | Custom hooks export identifiers starting with use followed by an uppercase letter. | A function that calls hooks but does not start with use. | | forwardRef for ref-receiving components | Components that need to receive ref (typically design-system primitives, focusable controls) use forwardRef. React 19's ref as a prop is also accepted. | A component renders a focusable element that consumers would expect to ref, but does not forward refs and is not React-19 ref-as-prop. | | displayName on memo/forwardRef wrappers | Components wrapped in React.memo or forwardRef set displayName so the React DevTools tree is readable. | Wrapped components with no displayName. | | Children prop typed | When a component accepts children, the prop is typed React.ReactNode (not any, not JSX.Element, which is too narrow). | children: any or children: JSX.Element in component prop types. | | One component or hook per file | Files export one primary component or hook plus its supporting types and helpers, not a grab-bag of unrelated components. Soft check — reported as partial. | Files exporting more than three independent components or hooks. | | Named exports for components | Components are exported as named exports, not default exports. Improves grep-ability and tooling. Soft check — reported as partial. | Default-exported components in projects that otherwise use named exports. |
Layer 3 — State management
| Check | Expectation | Violation signal | | --- | --- | --- | | Form state via a form library | Forms with more than five fields, or with non-trivial validation, use a dedicated form library (react-hook-form, formik). Native form state is acceptable for trivial forms. Soft check — reported as partial. | Complex forms hand-rolled with a useState per field. | | Server state in the data layer | Server-fetched data lives in TanStack Query, SWR, RTK Query, Apollo Client, or framework-native loaders — not in component-local useState populated by useEffect+fetch. (Overlap with /performance-audit and /architecture-audit; all three surface.) | Fetched-data state held in component state with no query layer involved. | | Single global state library | Exactly one of Redux Toolkit, Zustand, Jotai, MobX, or React Context as the global state strategy. (Overlap with /architecture-audit; both surface so a single fix passes both.) | Multiple global state libraries in the same application. | | No state synchronisation between sources of truth | Data is stored in one place and derived elsewhere; the same value isn't held in component state and a global store and a URL parameter at once. | Detected duplicate-state patterns. | | State at the right level | Local state lives in the component that uses it; lifted state lives at the lowest common ancestor; global state is for genuinely global concerns (auth, theme, feature flags) — not "anything used in two places". Soft check — reported as partial. | Global state holding values used by a single subtree, or local state for values used across the application. | | URL state for shareable or recoverable state | Search queries, filters, sort orders, current page, and selected tabs live in URL parameters when the user would expect them to survive a reload. Soft check — reported as partial. | Detected patterns where a refresh would lose UI state that the user expects to keep. |
Layer 4 — React 18/19 idioms (skipped silently when React below 18)
| Check | Expectation | Violation signal | | --- | --- | --- | | useId for accessible IDs | Generated IDs used to wire up labels, ARIA attributes, and form controls use useId rather than ad-hoc counters or random strings. (Overlap with /accessibility-audit.) | Hand-rolled ID generation when React 18+ is available. | | useTransition or startTransition for non-urgent updates | Updates that can be deferred (filtering large lists, search-as-you-type, navigation in a Suspense-aware app) use useTransition or startTransition. Soft check — reported as partial. (Overlap with /performance-audit.) | Detected hot patterns where transitions would help and are absent. | | Suspense boundaries for async data | When the framework or library supports Suspense for data fetching (React 19 use(), TanStack Query Suspense mode, framework loaders), Suspense boundaries with meaningful fallbacks are used. | Async data fetched without surrounding Suspense in projects that have opted into Suspense-based data fetching. | | 'use client' used minimally and deliberately (Next.js App Router only) | Client-component boundaries sit at the leaves of the tree, not at the root. (Overlap with /architecture-audit.) Soft check — reported as partial when more than half of components are client components. | Excessive 'use client' fragmentation; or 'use client' files importing server-only modules. Skipped silently outside App Router. | | Server Actions used appropriately (Next.js App Router only) | Mutating operations in App Router projects use Server Actions ('use server') when appropriate, rather than client-side fetch to a route handler that simply mutates. Soft check — reported as partial. | Patterns where Server Actions would be the natural choice but route handlers are used instead. Skipped silently outside App Router. | | useOptimistic for optimistic mutations (React 19 only) | Optimistic UI updates use useOptimistic rather than hand-rolled optimistic state. Skipped silently for React 18.x. | Hand-rolled optimistic updates in React 19 codebases. |
What this skill does
- Reads the knowledge graph when present. Soft dependency: when
graphify-out/graph.jsonexists, the audit identifies widely-consumed custom hooks (a hook used by 50 components is more important to get right than one used by 2) and prioritises them in the implementation plan. The audit still runs in full when the graph is absent. - Confirms a TypeScript and React project. Detects
package.json,tsconfig.json, andreactin dependencies. If any are absent, the skill stops and tells the user it currently supports TypeScript and React projects only. - Resolves the React version from
package.jsonand the lockfile. Determines whether layer 4 runs (React 18 or newer) or is reported as skipped. - Detects framework, form library, animation library, and design system for the diagnostic snapshot and for layer-specific dispatch (App Router gates tw
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: BenSheridanEdwards
- Source: BenSheridanEdwards/ArchitectPlaybook
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.