/code-review - Rigorous React/TypeScript Code Reviewer
Launches the code-reviewer agent to perform a comprehensive React/TypeScript/PWA code review of your changes.
Usage
# Review current branch changes /code-review # Review specific files /code-review src/components/CartSummary.tsx # Review with specific instructions /code-review focus on memoization issues in the dashboard widgets
Search Tools
Use the right tool for the job:
| Need | Tool | Example |
|---|---|---|
| Find where a prop or hook is used | Grep | Grep("useCheckoutTotal", "src/features") |
| Discover structural patterns (hooks, JSX) | ast-grep | ast-grep --pattern "useEffect($$)" src/dashboard |
| Jump to symbol definitions or references | LSP | lsp_symbols src/components/Chart.tsx |
| Find files by glob pattern | Glob | Glob("**/*.tsx") |
| Trace bundle boundaries / imports | Grep + ast-grep | ast-grep --pattern "import $$ from '$MODULE'" src |
For code review, prefer ast-grep + LSP when:
- •Verifying hook usage order and dependency arrays
- •Finding all consumers of a context/provider pair
- •Discovering related prop interfaces to check for backwards compatibility
What Gets Reviewed
The code reviewer is extremely strict and checks for:
Component Architecture
- •Business logic/data shaping embedded inside presentation components (should live in hooks or shared libs)
- •Side effects triggered during render instead of inside
useEffect - •Prop drilling across more than two layers without context or composition
- •"God components" that mix orchestration, view logic, and data fetching
Red flags:
// BAD: Business logic and network calls in render
export const CheckoutSummary = ({ cart }: { cart: LineItem[] }) => {
const total = cart.reduce((sum, item) => sum + item.price * item.qty, 0);
if (total > 1000) {
analytics.track('whale'); // fires on every render
}
fetch('/api/checkout/fees'); // runs during render
return <span>{formatCurrency(total)}</span>;
};
// BAD: Prop drilling through every layout layer
<App user={user}>
<Layout user={user}>
<Sidebar user={user}>
<Nav user={user} />
</Sidebar>
</Layout>
</App>;
// GOOD: Extract domain logic into a hook
function useCheckoutTotal(cart: LineItem[]): number {
return useMemo(
() => cart.reduce((sum, item) => sum + item.price * item.qty, 0),
[cart]
);
}
export const CheckoutSummary = () => {
const cart = useCartContext();
const total = useCheckoutTotal(cart);
useEffect(() => {
if (total > 1000) analytics.track('whale');
}, [total]);
return <Amount value={total} />;
};
// GOOD: Replace prop drilling with context/provider
const UserProvider = ({ user, children }: PropsWithChildren<{ user: User }>) => (
<UserContext.Provider value={user}>{children}</UserContext.Provider>
);
Code Quality (TypeScript-Focused)
- •
anytype usage leaking into exported APIs - •Missing explicit return types on exported hooks/functions/components
- •Magic strings/numbers that should be enums or constants
- •Barrel exports that re-export everything and introduce circular dependencies
Red flags:
// BAD: any everywhere
export function buildPayload(data: any) {
return data['value'] || 'default';
}
// BAD: Missing return type on exported hook
export const usePrice = (sku) => {
const [price, setPrice] = useState(0);
/* ... */
return price;
};
// BAD: Magic values sprinkled inline
if (plan === 'enterprise') {
setTimeout(startTrial, 45000);
}
// BAD: Barrel export cycle
// src/features/index.ts
export * from './FeatureProvider';
export * from './useFeature';
// src/features/useFeature.ts
import { FeatureProvider } from '.'; // circular import
// GOOD: Narrow types and explicit returns
export interface CheckoutPayload {
sku: string;
quantity: number;
}
export function buildPayload(data: CheckoutPayload): string {
return JSON.stringify(data);
}
export const usePrice = (sku: string): number => {
const [price, setPrice] = useState<number>(0);
/* ... */
return price;
};
// GOOD: Shared enums/constants and explicit exports
export enum PlanTier {
Starter = 'starter',
Enterprise = 'enterprise',
}
export const REQUEST_TIMEOUT_MS = 30_000;
export { FeatureProvider } from './FeatureProvider';
export { useFeature } from './useFeature';
Test Quality (React Focus)
- •Duplicate React Testing Library/Jest tests that should be parameterized with
it.each - •Tests asserting implementation details (class names, state) instead of accessible behavior
- •Missing cleanup for fake timers, mocks, or global spies between tests
- •Async interactions not awaited (
userEvent.clickwithoutawait) - •Snapshot-only coverage for complex conditional UI
Red flags:
// BAD: Copy/paste tests
it('renders primary button', () => {
render(<Button variant="primary" />);
expect(screen.getByText('Save')).toHaveClass('btn--primary');
});
it('renders secondary button', () => {
render(<Button variant="secondary" />);
expect(screen.getByText('Cancel')).toHaveClass('btn--secondary');
});
// BAD: Implementation detail assertion
const wrapper = shallow(<Toggle />);
expect(wrapper.find('.toggle__thumb').prop('data-state')).toBe('on');
// BAD: Missing await for async user interaction
userEvent.click(screen.getByRole('button', { name: /submit/i }));
expect(mockSubmit).toHaveBeenCalled(); // may run before promise resolves
// GOOD: Parameterized, user-facing assertions
it.each`
variant | label
${'primary'} | ${'Save'}
${'secondary'} | ${'Cancel'}
`('renders $variant button', ({ variant, label }) => {
render(<Button variant={variant}>{label}</Button>);
expect(screen.getByRole('button', { name: label })).toBeEnabled();
});
// GOOD: Focus on behavior, await async APIs
await userEvent.click(screen.getByRole('button', { name: /submit/i }));
await waitFor(() => expect(mockSubmit).toHaveBeenCalled());
// GOOD: Cleanup timers/mocks
afterEach(() => {
vi.useRealTimers();
vi.restoreAllMocks();
});
Frontend Security
Input validation & XSS:
- •
dangerouslySetInnerHTMLfed by unsanitized user content - •Directly rendering user data without encoding or escaping
Secrets & sensitive data:
- •API keys, tokens, or customer secrets committed to client bundles
- •Storing sensitive data in
localStorage/sessionStorageinstead ofhttpOnlycookies
Prototype pollution & unsafe merges:
- •
Object.assign({}, userInput)or{ ...userInput }without filtering keys - •Merging untrusted data into global config objects
Red flags:
// BAD: Unsanitized HTML injection
const Comment = ({ html }: { html: string }) => (
<div dangerouslySetInnerHTML={{ __html: html }} />
);
// BAD: Shipping secrets
export const config = {
mapsApiKey: 'pk_live_abc123',
};
// BAD: Storing sensitive tokens client-side
localStorage.setItem('refreshToken', token);
// BAD: Prototype pollution
const payload = { ...userInput };
Object.assign(globalTheme, payload);
// GOOD: Sanitize or render as text
const sanitized = DOMPurify.sanitize(html);
return <div dangerouslySetInnerHTML={{ __html: sanitized }} />;
// GOOD: Secrets fetched from server-only env vars
const { data } = await fetch('/api/config');
// GOOD: Keep sensitive data in httpOnly cookies; store opaque ids only
document.cookie = `session=${sessionId}; Secure; SameSite=Lax`;
// GOOD: Validate and whitelist keys before merging
const safe = pick(userInput, ['theme', 'density']);
const merged = { ...defaultTheme, ...safe };
React Performance
- •Components doing heavy work without
React.memo - •Inline object/function creation inside JSX causing prop identity churn
- •Missing
useMemo/useCallbackaround stable dependencies - •Rendering thousands of nodes without virtualization/windowing
- •Bundle bloat from importing whole libraries instead of subpaths; unused code not tree-shaken
- •Memory leaks:
useEffectwithout cleanup, stray timers, lingering event listeners
Red flags:
// BAD: Inline functions + no memoization
export const ExpensiveList = ({ items }: { items: Item[] }) => (
<div>
{items.map((item) => (
<Row key={item.id} item={item} onSelect={() => select(item.id)} />
))}
</div>
);
// BAD: Giant list rendered all at once
{
users.map((user) => <UserRow key={user.id} user={user} />);
}
// BAD: Unbounded imports
import _ from 'lodash';
const formatted = _.startCase(name);
// BAD: Missing cleanup
useEffect(() => {
window.addEventListener('resize', onResize);
}, []); // never removed
// GOOD: Memo + stable callbacks
const Row = React.memo(({ item, onSelect }: RowProps) => {
/* ... */
});
const handleSelect = useCallback((id: string) => select(id), [select]);
export const ExpensiveList = ({ items }: Props) => (
<div>
{items.map((item) => (
<Row key={item.id} item={item} onSelect={handleSelect} />
))}
</div>
);
// GOOD: Virtualization & focused imports
import startCase from 'lodash/startCase';
const rows = useVirtualizer({ count: users.length, estimateSize: () => 48 });
// GOOD: Cleanup effects
useEffect(() => {
window.addEventListener('resize', onResize);
return () => window.removeEventListener('resize', onResize);
}, [onResize]);
React/TypeScript Specific
- •Violations of the Rules of Hooks (conditional hooks, hooks inside loops)
- •Stale closures inside
useEffect/useCallback - •Missing dependency arrays or intentionally empty arrays without refs
- •Mixing controlled and uncontrolled component state
- •Misusing React keys (array index for dynamic lists)
- •Forgetting
forwardRefwhen exposing DOM nodes - •Missing error boundaries around critical UI segments
Red flags:
// BAD: Conditional hook
if (isAdmin) {
const data = useDashboardData();
}
// BAD: Missing deps leading to stale closure
useEffect(() => {
fetchData(filter);
}, []); // ignores filter updates
// BAD: Controlled/uncontrolled mix
<input defaultValue={value} value={value} onChange={setValue} />;
// BAD: Index as key
{
items.map((item, index) => <Row key={index} item={item} />);
}
// GOOD: Hooks called unconditionally
const data = useDashboardData({ role: isAdmin ? 'admin' : 'user' });
// GOOD: Stable dependencies or refs
const stableFilter = useDeepCompareMemoize(filter);
useEffect(() => {
fetchData(stableFilter);
}, [stableFilter]);
// GOOD: Controlled OR uncontrolled, not both
const [value, setValue] = useState('');
<input value={value} onChange={(e) => setValue(e.target.value)} />;
// GOOD: Deterministic keys & proper refs
{
items.map((item) => <Row key={item.id} item={item} />);
}
const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => (
<input ref={ref} {...props} />
));
<ErrorBoundary fallback={<CrashScreen />}>
<CriticalPanel />
</ErrorBoundary>;
Return Value Semantics (Critical)
Pattern: "Return Value Conflation" - When a function/promise returns the same value for cases requiring different handling.
- •Boolean returns: Does
falsemean "already handled" or "request failed"? - •
undefined/null: Does missing data mean "not found" or "error fetching"? - •Promise resolution vs rejection: Are rejections only thrown for network errors while validation issues slip through as
null?
Red flags:
// BAD: Conflates duplicate purchase with missing customer
export async function activatePremium(
orderId: string,
customerId: string
): Promise<boolean> {
const response = await fetch(`/api/customers/${customerId}/activate`, {
method: 'POST',
body: JSON.stringify({ orderId }),
});
if (!response.ok) {
return false; // could be 404, 409, or 500
}
return true;
}
if (!(await activatePremium(orderId, user.id))) {
// UI silently ignores: no refund, no error state
}
// GOOD: Distinguish each case explicitly
export type ActivationResult =
| { status: 'success' }
| { status: 'duplicate'; orderId: string }
| { status: 'user-missing'; customerId: string }
| { status: 'error'; message: string };
export async function activatePremium(
orderId: string,
customerId: string
): Promise<ActivationResult> {
const response = await fetch(`/api/customers/${customerId}/activate`, {
method: 'POST',
body: JSON.stringify({ orderId }),
});
if (response.status === 409) return { status: 'duplicate', orderId };
if (response.status === 404) return { status: 'user-missing', customerId };
if (!response.ok) return { status: 'error', message: await response.text() };
return { status: 'success' };
}
Async State Management
- •Race conditions in
useEffectwhere async work resolves after unmount and still callssetState - •Missing
AbortController/signalwhen canceling fetches - •Concurrent state updates that should use functional
setState - •IndexedDB/Dexie live queries not canceled on dependency change
Red flags:
// BAD: setState after unmount
useEffect(() => {
let active = true;
fetchUser().then(user => {
if (active) setUser(user);
});
}, []); // never flips active, setState after unmount
// BAD: No abort controller
useEffect(() => {
fetch(`/api/search?q=${query}`).then(setResults);
}, [query]);
// BAD: Concurrent updates using stale value
onScroll={() => setOffset(offset + 1)};
// GOOD: Abort + cleanup
useEffect(() => {
const controller = new AbortController();
fetch(`/api/search?q=${query}`, { signal: controller.signal })
.then(resp => resp.json())
.then(setResults)
.catch(err => {
if (err.name !== 'AbortError') throw err;
});
return () => controller.abort();
}, [query]);
// GOOD: Functional updates prevent stale closures
onScroll={() => setOffset(prev => prev + 1)};
// GOOD: Dexie liveQuery cleanup
useEffect(() => {
const sub = liveQuery(() => db.todos.where({ status }).toArray()).subscribe(setTodos);
return () => sub.unsubscribe();
}, [status]);
React Lifecycle
- •
useEffectmissing cleanup functions (subscriptions, observers) - •Event listeners registered but never removed
- •
setInterval/setTimeoutwithoutclearInterval/clearTimeout - •Dexie
liveQuery, Firebase, or socket subscriptions not cleaned up when dependencies change
Red flags:
// BAD: Window listener leaks
useEffect(() => {
window.addEventListener('resize', onResize);
}, []);
// BAD: Interval never cleared
useEffect(() => {
const id = setInterval(poll, 1000);
}, []);
// BAD: Firestore subscription persists
useEffect(() => {
firebase.firestore().collection('rooms').onSnapshot(setRooms);
}, []);
// GOOD: Cleanup everything
useEffect(() => {
window.addEventListener('resize', onResize);
return () => window.removeEventListener('resize', onResize);
}, [onResize]);
useEffect(() => {
const id = setInterval(poll, 1000);
return () => clearInterval(id);
}, [poll]);
useEffect(() => {
const unsubscribe = firebase.firestore().collection('rooms').onSnapshot(setRooms);
return () => unsubscribe();
}, []);
Browser Resources
- •Fetch requests kicked off without
AbortControlleror timeout management - •Web Workers spawned but never terminated
- •
BroadcastChannelnot closed when component unmounts - •Service Worker registrations not checked for outdated versions or errors
Red flags:
// BAD: Unbounded fetch
const promise = fetch('/api/report'); // never canceled if user navigates away
// BAD: Worker leak
const worker = new Worker(new URL('./heavy.ts', import.meta.url));
worker.postMessage(payload); // no termination
// BAD: BroadcastChannel leak
const channel = new BroadcastChannel('notifications');
channel.onmessage = handleMessage;
// GOOD: Abort fetch and clear references
const controller = new AbortController();
fetch('/api/report', { signal: controller.signal });
return () => controller.abort();
// GOOD: Terminate workers/broadcast channels
useEffect(() => {
const worker = new Worker(new URL('./heavy.ts', import.meta.url));
worker.postMessage(payload);
return () => worker.terminate();
}, [payload]);
useEffect(() => {
const channel = new BroadcastChannel('notifications');
channel.onmessage = handleMessage;
return () => channel.close();
}, []);
// GOOD: Guard service worker registration
navigator.serviceWorker.register('/sw.js').then((reg) => {
reg.update();
});
IndexedDB/Dexie.js Data Access
- •Using
.toArray()on large tables without bounding queries - •Missing indexes for frequently queried fields
- •Schema migrations that fail to bump versions or handle upgrades
- •No handling for
QuotaExceededErroror storage full scenarios
Red flags:
// BAD: Unbounded query pulling entire table
const allLogs = await db.logs.toArray();
// BAD: Missing index and filtering client-side
const tasks = await db.tasks.toArray();
const open = tasks.filter((t) => t.status === 'open');
// BAD: Migration ignored
db.version(3).stores({ tasks: '++id' }); // new fields but no upgrade handling
// BAD: No quota handling
await db.files.add(blob); // QuotaExceededError crashes app
// GOOD: Bounded queries + indexes
db.version(4).stores({ tasks: '++id,status,assigneeId' });
const open = await db.tasks.where('status').equals('open').limit(100).toArray();
// GOOD: Upgrade hook
db.version(4)
.stores({ tasks: '++id,status,assigneeId' })
.upgrade((tx) =>
tx
.table('tasks')
.toCollection()
.modify((task) => (task.slug ??= slugify(task.title)))
);
// GOOD: Graceful quota handling
try {
await db.files.add(blob);
} catch (error) {
if ((error as DOMException).name === 'QuotaExceededError') {
await evictOldFiles();
throw new Error('Storage full, try again after clearing space');
}
throw error;
}
Temporal & Time Handling
- •Mixing
Date.now()withnew Date()objects from different timezones - •Persisting local times without timezone/offset info
- •Hardcoded timezone math (e.g., subtracting hours manually)
- •Assuming 24 hours per day (DST issues)
- •No abstraction for time during testing (difficult to freeze time)
Red flags:
// BAD: Mixing local and UTC
const createdAt = new Date(); // local
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // treated as UTC elsewhere
// BAD: Hardcoded timezone adjustments
const est = utcDate.getTime() - 4 * 60 * 60 * 1000;
// BAD: Direct Date.now in business logic
if (Date.now() > subscription.expiresAt) {
cancel();
}
// GOOD: Always use ISO strings + DateTime libraries
const now = Temporal.Now.instant();
const expiresAt = now.add({ days: 1 });
// GOOD: Convert with Intl/Temporal APIs
const nyTime = now.toZonedDateTimeISO('America/New_York');
// GOOD: Inject clock during tests
const clock = useClock();
if (clock.now() > subscription.expiresAt) {
cancel();
}
Props API & Monitoring
- •Breaking prop interface changes without migration path
- •Missing prop validation / defaults (required vs optional) causing runtime
undefined - •Not wrapping remote widgets in error boundaries
- •
console.log/debuggerremnants shipped to production - •No integration with error reporting tools (Sentry, LogRocket, etc.) for UI crashes
Red flags:
// BAD: Breaking prop rename without alias
export interface ButtonProps {
onSubmit: () => void; // renamed from onClick without backwards compat
}
// BAD: Required prop treated as optional
const Avatar = ({ src }: { src?: string }) => <img src={src} />; // crashes when undefined
// BAD: No error boundary
<RemoteWidget />;
// BAD: Console noise in prod
console.log('Render metrics', data);
// GOOD: Provide shims/deprecations
export interface ButtonProps {
onClick: () => void;
/** @deprecated use onClick */
onSubmit?: () => void;
}
// GOOD: Validate props via runtime guards or defaults
const Avatar: FC<{ src: string }> = ({ src, ...rest }) => (
<img src={src ?? fallbackAvatar} {...rest} />
);
// GOOD: Wrap third-party components
<ErrorBoundary fallback={<WidgetCrashed />}>
<RemoteWidget />
</ErrorBoundary>;
// GOOD: Structured monitoring
console.debug = noop;
reportErrorToSentry(error, context);
Documentation Completeness (REQUEST CHANGES if gaps found)
Documentation gaps are not advisory — they are blocking. If any of the following are missing or outdated after a change, the verdict MUST be REQUEST CHANGES.
What must be kept in sync:
- •README.md: New features, changed scripts, new environment variables, updated project structure, changed tech stack entries
- •ROADMAP.md: Task completion status, sprint progress, checklist items
- •AGENTS.md: Architecture rules, build commands, project structure if files/folders were added or removed
- •JSDoc/TSDoc: All exported hooks, functions, and components must have doc comments explaining behavior and edge cases
- •Inline comments: Non-obvious logic, workarounds, or browser-specific behavior
When to flag:
- •A new component/hook was added but README project structure was not updated
- •A new script was added to package.json but not documented in README
- •A roadmap task was implemented but ROADMAP.md still shows it as pending
- •An exported function has no TSDoc comment
- •A PR adds a feature listed in the roadmap but doesn't check off the corresponding checklist item
- •Architecture rules in AGENTS.md no longer match actual code (e.g., new folders, changed dependency direction)
Red flags:
// BAD: Exported hook undocumented
export function usePaymentsGateway() {
/* ... */
}
// GOOD: TSDoc explains behavior and edge cases
/**
* usePaymentsGateway pulls the current PSP config, refreshes every 5 minutes,
* and throws when credentials are missing. Consumers must catch errors.
*/
export function usePaymentsGateway(): PaymentsGatewayState {
/* ... */
}
Verdict Decision Guide
| Condition | Verdict |
|---|---|
| No issues found | APPROVE |
| Minor style/naming suggestions only | APPROVE (with comments) |
| Documentation gaps (README, ROADMAP, AGENTS.md, TSDoc) | REQUEST CHANGES |
| Bugs, missing error handling, test gaps | REQUEST CHANGES |
| Security vulnerabilities, data loss, crash risks | BLOCK MERGE |
Documentation gaps always block. A feature without updated docs is an incomplete feature.
Review Output
The review is saved based on context:
When working on an issue:
.md/issues/{issue-number}/code-review.md
.md/issues/{issue-number}/code-review-round-2.md # subsequent rounds
When not on an issue (standalone):
.md/standalone/code-review-{branch-name}.md
.md/standalone/code-review-{date}.md # if no branch context
Examples:
- •Issue #123:
.md/issues/123/code-review.md - •Branch
feat/pwa-offline:.md/standalone/code-review-feat-pwa-offline.md - •No context:
.md/standalone/code-review-2026-01-29.md
The file includes:
Header Section
- •Context: Branch name, related issue number (if applicable), or a brief description of the work being reviewed
- •Review Date: Timestamp of the review
Review Sections
- •Critical Issues: Blockers that must be fixed (security, data loss, crashes)
- •Component Architecture: Hook discipline, prop boundaries, component responsibilities
- •React Performance & Async State: Memoization, virtualization, fetch cancellation
- •React Lifecycle & Resources: Cleanup for effects, timers, workers, subscriptions
- •Return Value Semantics: Ambiguous returns, silent failures, conflated error cases
- •Client Data Access: IndexedDB/Dexie usage, schema, migrations
- •Props API & Monitoring: Contract issues, logging/monitoring problems
- •Code Quality & Tests: Type safety, code smells, test coverage gaps
- •Documentation Gaps: Missing updates to README, ROADMAP, AGENTS.md, or TSDoc (triggers REQUEST CHANGES)
- •Verdict: APPROVE / REQUEST CHANGES / BLOCK MERGE
Footer Section (MANDATORY)
- •Model: The exact model ID that performed this review (e.g., claude-sonnet-4-5, claude-opus-4-6)
- •Persona: The agent persona that wrote it (e.g., Sisyphus-Junior, Oracle)
Review Workflow
When conducting a code review, follow these steps:
- •
Gather Context Information:
- •Run
git branch --show-currentto get the current branch name - •Check if there's a related GitHub issue by:
- •Looking for issue numbers in commit messages (
git log --oneline) - •Checking branch name for patterns like
feat/123-descriptionorissue-123 - •Using
gh issue listif gh CLI is available
- •Looking for issue numbers in commit messages (
- •If no issue is found, extract a brief description from recent commits or branch name
- •Get the current model name and persona from your environment/config
- •Run
- •
Perform Code Review:
- •Run
git diff master...HEAD(ormain) to see all changes - •Read and analyze modified files (components, hooks, tests, Dexie stores, worker scripts)
- •Apply all review criteria listed above
- •Run
- •
Write Review File:
- •Determine output path based on context:
- •Issue context (from commits, branch name like
feat/123-*, orgh issue):- •First review:
.md/issues/{issue-number}/code-review.md - •Subsequent rounds:
.md/issues/{issue-number}/code-review-round-{N}.md
- •First review:
- •Branch only (no issue):
.md/standalone/code-review-{branch-name}.md - •No context:
.md/standalone/code-review-{date}.md
- •Issue context (from commits, branch name like
- •Create directories if they don't exist (
mkdir -p) - •Sanitize branch names for filename (replace
/with-, remove special chars) - •Follow the exact markdown format specified below
- •Include context information in the header
- •Include model name and persona in the footer
- •Determine output path based on context:
Markdown Output Format
The review markdown file MUST follow this structure:
# Code Review **Context**: [Branch: feature/xyz] OR [Issue #123: Description] OR [Brief description of changes] **Date**: YYYY-MM-DD HH:MM --- ## Critical Issues [Security vulnerabilities, data loss risks, crash scenarios] ## Component Architecture [Hook discipline, prop boundaries, component responsibilities] ## React Performance & Async State [Memoization, virtualization, fetch cancellation, concurrency issues] ## React Lifecycle & Resources [Effect cleanup, timers, workers, subscriptions, browser resources] ## Return Value Semantics [Ambiguous returns, silent failures, conflated error cases] ## Client Data Access [IndexedDB/Dexie usage, schema, migrations, quota handling] ## Props API & Monitoring [Contract issues, monitoring gaps, error boundaries, logging] ## Code Quality & Tests [Type safety, code smells, Jest/RTL issues] ## Documentation Gaps [Missing updates to README, Storybook, MDX, ADRs] ## Verdict [APPROVE / REQUEST CHANGES / BLOCK MERGE] --- **Model**: [e.g., claude-sonnet-4-5, claude-opus-4-6] **Persona**: [e.g., Sisyphus-Junior, Oracle]
Example
# After making changes for issue #123 /code-review # The agent will: # 1. git diff master...HEAD to see all changes # 2. Detect issue context (from branch name feat/123-description or commits) # 3. Read modified files (components, hooks, tests, Dexie stores, workers) # 4. Analyze against React/TypeScript best practices (including memoization, hook rules, Dexie bounds) # 5. Write review to .md/issues/123/code-review.md
Notes
- •The reviewer is intentionally strict - don't take it personally
- •Reviews focus on the changes, not the entire codebase
- •If you disagree with feedback, that's valid - use your judgment
- •The review can be resumed with additional instructions if needed