skills/typescript/SKILL.md
TypeScript coding standards and type safety conventions. Use when: creating TypeScript files, defining interfaces and types, writing type-safe code, reviewing TypeScript for type correctness, auditing a codebase for type safety gaps, eliminating any or ts-ignore usage, or improving strict-mode compliance. Covers strict typing, avoiding any and ts-ignore, discriminated unions, Zod runtime validation, immutability patterns, and proper type definitions.
npx skillsauth add michaelsvanbeek/personal-agent-skills typescriptInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
@ts-ignore - fix the underlying type issue instead.any type - use unknown if the type is truly unknown, then narrow with type guards.interface for object shapes that may be extended; use type for unions, intersections, and computed types.// Prefer interfaces for object shapes
interface User {
id: string;
name: string;
email: string;
role: UserRole;
}
// Use type for unions and computed types
type UserRole = "admin" | "editor" | "viewer";
type UserMap = Record<string, User>;
// Use generics for reusable patterns
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
Use discriminated unions for any type that can be one of several variants. Add a literal type field as the discriminator:
// Define variants with a shared discriminator field
interface LoadingState {
type: "loading";
}
interface SuccessState<T> {
type: "success";
data: T;
}
interface ErrorState {
type: "error";
error: string;
retryable: boolean;
}
type AsyncState<T> = LoadingState | SuccessState<T> | ErrorState;
// TypeScript narrows automatically in switch/if blocks
function render(state: AsyncState<User[]>) {
switch (state.type) {
case "loading":
return <Spinner />;
case "success":
return <UserList users={state.data} />; // data is typed as User[]
case "error":
return <ErrorMessage message={state.error} />;
}
}
| Use case | Example |
|----------|---------|
| State machines | idle → loading → success \| error |
| Command/event types | { type: "create" } \| { type: "update" } \| { type: "delete" } |
| API responses | Different shapes per endpoint or error type |
| Configuration variants | { transport: "stdio" } \| { transport: "http", url: string } |
string.switch with exhaustive checks — add a default: never assertion to catch unhandled variants:function assertNever(x: never): never {
throw new Error(`Unexpected variant: ${JSON.stringify(x)}`);
}
function handleCommand(cmd: Command) {
switch (cmd.type) {
case "create": return handleCreate(cmd);
case "update": return handleUpdate(cmd);
case "delete": return handleDelete(cmd);
default: return assertNever(cmd); // compile error if a variant is missing
}
}
Use Zod to validate data at system boundaries — API responses, config files, user input, environment variables. Zod bridges the gap between TypeScript's compile-time types and runtime reality.
import { z } from "zod";
// Define schema — this is the single source of truth
const UserSchema = z.object({
id: z.string(),
name: z.string().min(1).max(100),
email: z.string().email(),
role: z.enum(["admin", "editor", "viewer"]),
});
// Derive the TypeScript type from the schema
type User = z.infer<typeof UserSchema>;
// Validate at runtime
function parseUser(data: unknown): User {
return UserSchema.parse(data); // throws ZodError on invalid input
}
// Safe parse (returns result instead of throwing)
const result = UserSchema.safeParse(data);
if (result.success) {
console.warn(result.data.name); // typed as User
} else {
console.error(result.error.issues);
}
| Boundary | Example | |----------|---------| | External API responses | Parse JSON before trusting it | | Configuration files | Validate settings, frontmatter, manifests | | Environment variables | Validate and type env vars at startup | | User input | Form data, URL params, CLI arguments | | Webhook payloads | Verify structure before processing |
// Discriminated union schemas
const EventSchema = z.discriminatedUnion("type", [
z.object({ type: z.literal("click"), x: z.number(), y: z.number() }),
z.object({ type: z.literal("keypress"), key: z.string() }),
]);
// Coercion for env vars (strings → typed values)
const EnvSchema = z.object({
PORT: z.coerce.number().default(3000),
DEBUG: z.coerce.boolean().default(false),
DATABASE_URL: z.string().url(),
});
// Reusable schema composition
const PaginationSchema = z.object({
limit: z.number().int().min(1).max(100).default(25),
cursor: z.string().optional(),
});
z.infer<>, don't duplicate.safeParse for user-facing errors — returns structured error details. Use parse for internal assertions where failure is a bug.Enforce immutability at the type level to prevent accidental mutations in shared state:
// Deep immutable wrapper — prevents mutations at any depth
type DeepImmutable<T> = T extends Map<infer K, infer V>
? ReadonlyMap<DeepImmutable<K>, DeepImmutable<V>>
: T extends Set<infer S>
? ReadonlySet<DeepImmutable<S>>
: T extends object
? { readonly [K in keyof T]: DeepImmutable<T[K]> }
: T;
// Use for state that must not be mutated
interface AppState {
readonly settings: DeepImmutable<Settings>;
readonly permissions: DeepImmutable<PermissionRules>;
}
readonly on interface properties that should not be mutated after creation.ReadonlyArray<T> or readonly T[] for arrays that should not be modified.Readonly<T> for shallow immutability, DeepImmutable<T> for state shared across boundaries.{ ...prev, field: newValue }, never with assignment.Propagate AbortSignal through async operations for proper cancellation:
async function fetchData(url: string, signal: AbortSignal): Promise<Data> {
const response = await fetch(url, { signal });
return response.json() as Promise<Data>;
}
// Type guard for abort errors (multiple sources)
function isAbortError(error: unknown): boolean {
return (
error instanceof DOMException && error.name === "AbortError" ||
error instanceof Error && error.name === "AbortError"
);
}
// Usage with cleanup
const controller = new AbortController();
try {
const data = await fetchData("/api/users", controller.signal);
} catch (error) {
if (isAbortError(error)) return; // expected cancellation, not an error
throw error;
}
AbortSignal to all async functions that support cancellation.signal.aborted before expensive operations in long-running loops.strict mode in tsconfig.json.readonly for properties that should not be mutated.as const for literal type inference on constants.typeof, instanceof, and custom type guards over type assertions.satisfies operator to validate types while preserving inference:// satisfies checks the type without widening — preserves literal types
const ROUTES = {
home: "/",
about: "/about",
settings: "/settings",
} as const satisfies Record<string, string>;
// ROUTES.home is typed as "/" (literal), not string
Enable strict mode and recommended compiler options:
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"esModuleInterop": true,
"skipLibCheck": true
}
}
index.ts) sparingly and only at module boundaries.For React-specific patterns with TypeScript, TailwindCSS, and Vite, see the web-ui skill. For runtime validation at API boundaries using Zod, see the api-design skill.
testing
Writing clear, actionable tickets in any issue tracker (Jira, Linear, GitHub Issues, ServiceNow, etc.). Use when: creating epics, stories, tasks, bugs, or spikes; writing acceptance criteria; decomposing work for a sprint; linking dependencies between tickets; auditing backlog items for clarity; or coaching a team on ticket quality. Covers title conventions, description templates, acceptance criteria, decomposition rules, dependency linking, and org-specific pluggable configuration.
development
Testing strategy, patterns, and evaluation for software and LLM/AI systems. Use when: writing tests, choosing test boundaries, designing test data, structuring test suites, evaluating LLM outputs, building evaluation pipelines, setting coverage thresholds, auditing test coverage gaps in existing projects, or improving test quality and structure.
development
Writing effective status updates for different audiences and cadences. Use when: writing a weekly status update, preparing a monthly summary, drafting a quarterly review, sending updates to leadership, sharing progress with stakeholders, or improving the clarity and impact of team communications. Covers weekly, monthly, and quarterly formats tailored for upward, lateral, and downward communication.
development
--- name: statistics description: >- Statistical analysis and hypothesis testing for data-driven decisions. Use when: choosing the right statistical test for a question, calculating sample sizes, running A/B test analysis, comparing distributions, measuring correlation, building confidence intervals, validating assumptions before applying a test, interpreting p-values and effect sizes, or selecting the right summary statistics for a dataset. Covers descriptive statistics, hypothesi