From ts-dev-kit
Enforces TypeScript conventions for strict, type-safe code: no `any` use unknown, interfaces vs types, literal unions over enums, discriminated unions, type narrowing with guards, branded types, and anti-pattern avoidance.
npx claudepluginhub jgamaraalv/ts-dev-kit --plugin ts-dev-kitThis skill uses the workspace's default tool permissions.
Project-wide TypeScript standards that complement agent-specific instructions.
Guides TypeScript code with pedantic best practices: strict tsconfig flags like noUncheckedIndexedAccess, discriminated unions over assertions, Zod runtime validation, barrel exports, as const, ESLint rules.
Guides TypeScript type system usage: generics, utility types (Partial, Pick, Omit), type narrowing, guards, discriminated unions, strict mode, and patterns with zod/valibot.
Enforces TypeScript type safety rules including strict mode, no 'any', discriminated unions, and 'unknown' with type guards. Use when writing, reviewing types, or refactoring TS code.
Share bugs, ideas, or general feedback.
Project-wide TypeScript standards that complement agent-specific instructions.
any: Use unknown if the type is truly dynamic, then narrow.strict: true, noUncheckedIndexedAccess, verbatimModuleSyntax.Readonly<T>, Pick, Omit, and Record for precise types.UserId, OrderId) to prevent mixing.z.infer<typeof schema> over hand-written types when a Zod schema exists.extends and declaration merging.interface for plain objects, type for everything else.// Interface: object shape, extensible
interface User {
id: string;
name: string;
}
interface Employee extends User {
company: string;
}
// Type: union, intersection, computed
type Result = Success | Failure;
type UserProfile = User & { bio: string };
type Nullable<T> = { [K in keyof T]: T[K] | null };
// Prefer this
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
type Direction = "north" | "south" | "east" | "west";
// Over this (emits runtime JS)
enum HttpMethod { GET, POST, PUT, DELETE }
Add a type (or kind) literal field to each variant. Always handle exhaustiveness with assertNever.
interface Circle { type: "circle"; radius: number }
interface Square { type: "square"; side: number }
interface Triangle { type: "triangle"; base: number; height: number }
type Shape = Circle | Square | Triangle;
function assertNever(x: never): never {
throw new Error(`Unexpected value: ${x}`);
}
function area(shape: Shape): number {
switch (shape.type) {
case "circle": return Math.PI * shape.radius ** 2;
case "square": return shape.side ** 2;
case "triangle": return (shape.base * shape.height) / 2;
default: return assertNever(shape);
}
}
Always narrow before accessing type-specific properties.
typeof for primitives: typeof x === "string"in for object shapes: "swim" in petfunction isBook(item): item is Bookfunction format(input: string | number): string {
if (typeof input === "string") return input.toUpperCase();
return input.toFixed(2);
}
// Custom type guard
function isError(result: Result): result is ErrorResult {
return result.success === false;
}
extends — never assume properties exist on unconstrained T.T = unknown) when callers often use a single type.// Constrained generic
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
// With default
type ApiResponse<T = unknown> = { data: T; status: number };
Use mapped types to derive variants from a base — never duplicate type definitions.
// All fields optional (equivalent to built-in Partial<T>)
type Optional<T> = { [K in keyof T]?: T[K] };
// All fields nullable
type Nullable<T> = { [K in keyof T]: T[K] | null };
// Key remapping with template literals
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
& to compose smaller interfaces into richer types.never — check for this.type Timestamped = { createdAt: Date; updatedAt: Date };
type UserRecord = User & Timestamped;
// Type-only imports (required by verbatimModuleSyntax)
import type { FastifyInstance } from "fastify";
// Mixed imports: separate values and types
import { z } from "zod/v4";
import type { ZodType } from "zod/v4";
// ioredis: always named import
import { Redis } from "ioredis";
httpErrors or use reply.status().send() — the centralized setErrorHandler formats the response.NotFoundError, ConflictError).getUserById, createReport, isActive, hasPermissionis/has/can/should prefixget, find, list, fetchcreate, update, delete, add, remove<anti_patterns>
RATE_LIMITS, PAGINATION, CACHE)..length on string | number fails at runtime if it's a number.readonly is shallow: readonly prevents reassignment but doesn't freeze nested objects.<T> with no extends loses type info — constrain or use a concrete type.{ status: string } & { status: number } silently collapses to never.default: return assertNever(x) in discriminated union switches.</anti_patterns>