Performs structured code reviews on TypeScript codebases covering correctness, type safety, async patterns, security, testing, and style. Use when reviewing TypeScript code, pull requests, or when asked to do a code review on a TypeScript or Node.js project.
72
88%
Does it follow best practices?
Impact
—
No eval scenarios have been run
Passed
No known issues
When asked to review code — a file, a function, a PR diff, or a module — follow this checklist systematically. Always provide specific, actionable feedback with line references and concrete improvement suggestions. Never give vague praise or criticism.
Before writing any comment:
Check for bugs, edge cases, and incorrect assumptions.
?. chains that silently return undefined instead of throwing.typeof, instanceof, or a discriminated union guard used before accessing a narrowed type?noImplicitReturns flag these?== vs === — always use === in TypeScript.Example:
// ❌ Bug: silently returns undefined if user is not found — caller likely expects to throw
const name = users.find(u => u.id === id)?.name;
// ✅ Fix: be explicit about the missing case
const user = users.find(u => u.id === id);
if (!user) throw new Error(`User ${id} not found`);
const name = user.name;any usage: flag every any. Suggest unknown + type guard, or a proper interface.as): flag casts that bypass the type system without a guard. as unknown as X is almost always wrong.!): flag every ! postfix operator — it disables null checking. Require a comment explaining why it's safe, or replace with a guard.strict: true: if the tsconfig lacks strict: true, flag it. At minimum, strictNullChecks and noImplicitAny must be enabled.string | number | boolean | object — suggest a discriminated union or a named type.interface vs type: prefer interface for object shapes that may be extended; type for unions, intersections, and aliases.Example:
// ❌ Unsafe: assertion without evidence
const result = response.data as UserProfile;
// ✅ Safe: validate at the boundary
if (!isUserProfile(response.data)) throw new TypeError('Unexpected API response shape');
const result: UserProfile = response.data;Promise must be either await-ed, .catch()-ed, or explicitly void-ed with a comment.async functions: async functions called without await silently drop errors.fs.readFileSync, execSync, or CPU-heavy loops in an async function block the event loop — flag them.await calls that could run with Promise.all are a performance issue.Promise.all failure modes: Promise.all fails fast on the first rejection; use Promise.allSettled when partial failure is acceptable.await points without a lock is a race condition.Example:
// ❌ Sequential when independent
const user = await fetchUser(id);
const orders = await fetchOrders(id);
// ✅ Parallel
const [user, orders] = await Promise.all([fetchUser(id), fetchOrders(id)]);console.log / logger calls for request bodies, auth headers, or user data.req.body.x access without a schema (Zod, Valibot, or class-validator).Object.assign({}, userInput) or {...userInput} with untrusted input can pollute prototypes — prefer explicit field extraction.eval / Function(): flag any dynamic code execution.path.join(baseDir, userInput) without normalization and boundary-checking is a vulnerability.doX(data, true) call is opaque — suggest two functions or a discriminated union.catch (e) {} or catch (e) { return null; } hides failures — require logging and re-throwing or explicit handling.throw "something failed") loses stack traces — always throw Error instances or subclasses.Result<T, E> patterns over throwing for expected failures.// ❌ Loses the stack trace
throw "user not found";
// ✅
throw new UserNotFoundError(`User ${id} not found`);beforeEach/afterEach to reset.it('should <do X> when <condition>') — not it('test 1').expect.assertions(n): use in async tests to catch cases where the assertion is never reached.const over let: prefer const for all values that are not reassigned.import * as foo).camelCase for variables/functions, PascalCase for types/classes/components, SCREAMING_SNAKE_CASE for module-level constants.console.log: flag any left in production code — use a proper logger._unused that are not intentional; TypeScript's noUnusedLocals should catch these.Structure your response as follows:
A 2–4 sentence overview: what does the code do, is it generally well-written, what is the most important concern?
Issues that must be fixed before merging: security vulnerabilities, data loss risks, correctness bugs, broken contracts. Number each item.
Things that are not blocking but meaningfully improve quality: design issues, missing tests, performance problems, unclear naming. Number each item.
Style, convention, and cleanup items that improve consistency but have minimal functional impact. Can be bulleted for brevity.
Call out what is done well. This is not optional — good feedback is balanced.
any — use unknown + type guard or a proper typeas X) without a guard!) without justificationstrict: true (or at minimum strictNullChecks + noImplicitAny) in tsconfigPromise.allError instances, not stringsconsole.log left in production pathsc0b2e4b
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.