Let Kody automatically check quality, security and performance in every pull request—cloud or self-hosted, in under 2 minutes.
Check every pull request with this TypeScript code review checklist to maintain strong typing, solid security and reliable performance before production.
Here’s what this checklist helps you review before approving a pull request.
✅ Code & Design Quality – consistent naming and formatting, small focused functions, and clean modular structure
✅ Type Safety & Contracts – strict typing, precise generics, safe narrowing and runtime validation
✅ Async & Concurrency – explicit Promise types, proper cancellation, bounded retries and controlled parallelism
✅ Error Handling & Observability – typed errors with context, structured logs, metrics and health checks
✅ Security & Performance – validated inputs, safe DOM/secret handling, efficient data flows and scalable execution
✅ Data & Integration – well-typed APIs and schemas, safe migrations and correct handling of dates, numbers and configuration
Use these points as a starting guide to automate code reviews with your agent.
Naming is meaningful and consistent (files kebab-case; types/interfaces PascalCase; functions/variables camelCase; enums PascalCase).
Modules expose a minimal, focused surface (no “barrel dumping” everything from index.ts).
Functions are small and single-purpose; parameters ≤ ~5; avoid boolean flags—prefer options objects.
Comments explain “why,” not “what”; no stale or commented-out code; TODOs link to an issue.
Avoid clever type gymnastics in app code—move complex types to types.ts
with docs.
Prettier formatting and ESLint pass; no ts-ignore
/eslint-disable
unless justified with a comment.
strict
mode enabled; no implicit any; noImplicitOverride
, noUncheckedIndexedAccess
, exactOptionalPropertyTypes
turned on when possible.
Prefer unknown
over any
; model runtime-validated inputs as unknown
→ refine to safe types.
Discriminated unions used over ad-hoc flags; exhaustive switch
guarded by never
.
Narrowing uses type predicates or assertion functions (not as
casts); avoid non-null !
except at validated boundaries.
Use readonly
for immutable structures and tuples where applicable.
Use satisfies
to validate value shapes against types without widening.
Generics have clear type params (<TData, TError = unknown>
with defaults where useful).
Constraints reflect intent (<T extends Record<string, unknown>>
); avoid over-generic <T = any>
.
Conditional/mapped/template-literal types are readable and tested; avoid exponential complexity.
Output types are computed via helpers (e.g., Pick
, Omit
, Exclude
, NonNullable
, Awaited
)—don’t reinvent them.
Variance considered for callback params (don’t over-constrain contravariant positions).
Branded/opaque types used for IDs/unsafe primitives to prevent mix-ups.
All external inputs (env, JSON, network, user input) are validated at runtime before use.
Optional fields respect exactOptionalPropertyTypes
; avoid foo?.bar?.()
chains that hide errors.
Index access checks use noUncheckedIndexedAccess
or guards.
Nullable flows narrowed with explicit guards; no reliance on truthiness for 0/”/false.
Non-null assertions !
appear only after rigorous validation or in performance-critical hot paths with comments.
Public APIs expose minimal, stable contracts; breaking changes are versioned.
Types exported for consumers are intentionally designed (no leaking internal helpers).
DTOs/requests/responses are declared once and reused across layers (derive from schema when possible).
Error contracts are typed (error shapes or Result<E, T>
style); no throw string
.
Backwards compatibility considered (deprecated fields documented with removal plan).
Interop with JS code uses clear boundary modules; inputs validated and narrowed at the edge.
JSDoc typings in JS files are accurate if mixed repo; no duplication between JSDoc and .d.ts.
Avoid default exports for libraries; named exports ease tree-shaking and refactors.
ESM/CJS interop handled via exports
field and correct moduleResolution
(e.g., nodenext
).
allowJs
/checkJs
usage is intentional for migration, not permanent drift.
async
functions have explicit return types (Promise<...>
); no floating promises—guard with void
or await
.
Cancellation supported via AbortSignal
or equivalent; propagated through layers.
Backoff with jitter for retries; max attempts bounded; idempotency keys for mutating calls.
Avoid forEach(async ...)
; prefer for...of
with await
, or Promise.allSettled
with concurrency limits.
Timers/intervals cleaned up; no orphaned handles in long-lived services.
Shared state is synchronized (atomic updates, message queues, or locks when needed).
Throw Error
(or subclasses) with cause/context; never throw plain strings.
Errors are mapped at boundaries to typed domain errors; avoid leaking infra details upward.
Logs are structured (key-value), typed, and redact secrets; correlation/trace IDs propagate.
Metrics (latency, saturation, errors) and health checks exist for critical paths.
try/catch
blocks are minimal; don’t swallow errors—surface or log with level and action item.
Inputs sanitized/validated; no direct DOM injection; escape HTML; use textContent
over innerHTML
.
Avoid eval
, Function
, dynamic require
; no unsafe any
in security-sensitive areas.
Secrets not in source; use typed config; rotate and scope credentials; least privilege enforced.
HTTP security headers present on server; CSRF/XSRF, CORS, and content security policies set correctly.
Dependency risk managed (pin versions, review transitive impacts); sandbox untrusted code.
Hot paths avoid excessive allocations; prefer object reuse where safe; avoid megabyte-scale JSON in memory.
Data transforms fused where possible (map+filter
combined); avoid O(n²) surprises.
Awareness of V8/JIT deopts (stable object shapes, avoid polymorphic megamorphic call sites).
Use streaming/iterators for large payloads; backpressure respected.
Avoid synchronous blocking in event loops (crypto, zlib) without worker threads.
Unit tests cover logic and edge cases; integration tests for boundaries; E2E for critical flows.
Type-level tests (e.g., expectTypeOf
, tsd
) validate public types and generics.
Property-based or fuzz tests for parsers/serializers; snapshot tests used sparingly and focused.
Mocks/fakes are typed; avoid over-mocking—prefer real modules in integration tests.
Coverage meaningful (branches/conditions), not just lines; flakiness tracked and fixed.
typescript-eslint
rules enforce safety: no-unsafe-*
, no-floating-promises
, restrict-template-expressions
, consistent-type-imports
.
Lint and typecheck run in CI with --noEmit
; failing PRs block merge.
Dead code detection and dependency checks in CI; bundle size budgets enforced where applicable.
Commit hooks run fast (e.g., lint-staged
); no long-running local gates.
tsconfig
per package with clear target
, module
, moduleResolution
, lib
; composite
/project references for large repos.
Declarations (declaration
, declarationMap
) emitted for libraries; sourceMap
on for debuggability.
isolatedModules
compatible with the transpiler (swc/esbuild/babel) if used.
Reproducible builds (locked deps, pinned toolchain); environment-specific condition exports used instead of ad-hoc flags.
CI caches TypeScript build info; incremental builds enabled locally.
Proper package.json
fields: name
, version
, type
, exports
, types
, files
, sideEffects
, engines
, peerDependencies
.
Dual ESM/CJS support via conditional exports
; no deep import leaks.
typesVersions
provided if necessary; public API surface minimal and documented.
Monorepo uses workspaces (pnpm/npm/yarn), project refs, and consistent tooling; no cross-package path hacks.
Changesets or equivalent for versioning/release notes; publish pipeline typed and automated.
React: components typed (FC
avoided for children), props/interfaces minimal, hooks dependencies correct, useMemo/useCallback
purposeful.
Next.js/Nuxt/SvelteKit: server/client boundaries typed; no secret leakage to client; route handlers typed and validated.
Node/Nest/Express/Fastify: schemas (zod/yup/valibot) validate at edges; handlers return typed results/errors.
Angular: strictTemplates
enabled; inputs/outputs typed; DI tokens typed and scoped.
State management (Redux/Zustand/MobX/etc.) has typed selectors/actions; side effects isolated and testable.
HTTP clients are typed; request/response models derived from a single schema source (OpenAPI → types or zod → OpenAPI).
Retries use idempotent methods only; timeouts and AbortSignal
in place; circuit breakers for flaky deps.
Pagination, rate limits, and partial failures handled; streaming where applicable.
Authentication flows strongly typed; tokens/claims validated; clock skew handled.
WebSocket/Server-Sent Events types for messages and lifecycle events.
DB access layer typed end-to-end (e.g., Prisma/Typed SQL); migrations reviewed; defaults and constraints reflected in types.
bigint
/decimal handled safely (no silent number coercion); dates/timezones explicit (Temporal
/Date
strategy documented).
Serialization formats typed and versioned; backward compatibility planned.
Caching layers typed; invalidation keys modeled; TTLs explicit.
Clear boundaries between domain/app/infra; dependencies flow inward; no circular imports.
Public vs. internal types separated; unstable internals not exported.
Feature modules cohesive; avoid “god” utility modules; avoid overuse of re-exports that obscure ownership.
ADRs or lightweight design notes exist for non-obvious decisions; complexity budget respected.
Prefer composition over inheritance; Strategy/Command/Factory used where they reduce complexity.
Result/Either types over exceptions for expected failures; never for logic holes.
Builders fluent but typed (no unsafe chaining); brand types protect invariants.
Observables/iterators/async iterables typed across producers/consumers.
Config read once, validated (zod/valibot) and exported as typed singleton; no ad-hoc process.env
access in app code.
Feature flags typed (enum/union) with explicit rollout strategy; defaults safe; guards centralized.
Client/server flag evaluation boundaries respected; no leaking server-only flags to client bundles.
Twelve-factor: stateless services, config via env (typed), logs to stdout in JSON, graceful shutdown with signal handling.
Health/readiness probes typed and reflect real dependencies.
OpenTelemetry types for traces/metrics/logs; context propagation typed across async boundaries.
Resource limits/timeouts/cancellations enforced in code and infra config.
CLIs typed (e.g., oclif/commander) with clear help, validation, and exit codes.
Shell completions and --json
machine output where relevant.
Error messages actionable; verbose/debug modes; telemetry gated and documented.
Fast local dev loop (tsx/esbuild) without compromising type safety.
any
usage forbidden except at narrow, documented boundaries; prefer unknown
+ refinement.
as
casts minimized; if required, justified; no double-casting to bypass compiler.
Declaration merging/module augmentation documented and covered by tests.
ts-ignore
/expect-error
lines include rationale and a ticket to remove; time-boxed.
const
assertions and satisfies
used to keep literals precise without widening.
Let Kody automatically check quality, security and performance in every pull request—cloud or self-hosted, in under 2 minutes.