This skill should be used when writing TypeScript, eliminating any types, implementing Zod validation, or when strict type safety is needed. Covers modern TS 5.5+ features and runtime validation patterns.
Generates type-safe TypeScript code with Zod validation and modern patterns for strict runtime safety.
npx claudepluginhub outfitter-dev/outfitterThis skill inherits all available tools. When active, it can use any tool Claude has access to.
examples/api-response.mdexamples/form-validation.mdexamples/framework-adapters.mdexamples/resource-management.mdexamples/state-machine.mdreferences/advanced-types.mdreferences/branded-types.mdreferences/migration-paths.mdreferences/modern-features.mdreferences/result-pattern.mdreferences/tsdoc-patterns.mdreferences/zod-building-blocks.mdreferences/zod-integration.mdreferences/zod-performance.mdreferences/zod-schemas.mdType-safe code = compile-time errors = runtime confidence.
<when_to_use>
any typesNOT for: runtime-only logic unrelated to types, non-TypeScript projects
</when_to_use>
<config>tsconfig.json strict settings:
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"forceConsistentCasingInFileNames": true,
"verbatimModuleSyntax": true,
"isolatedModules": true,
"skipLibCheck": false
}
}
Version requirements: TS 5.2+ (using), TS 5.4+ (NoInfer), TS 5.5+ (inferred predicates)
<eliminating_any>
any defeats the type system. Use unknown + guards.
// ❌ NEVER
function process(data: any) {
return data.value;
}
// ✅ ALWAYS
function process(data: unknown): string {
if (!hasValue(data)) throw new TypeError("Invalid");
return data.value.toString();
}
function hasValue(v: unknown): v is { value: unknown } {
return typeof v === "object" && v !== null && "value" in v;
}
Validate at boundaries:
async function fetchUser(id: string): Promise<User> {
const data: unknown = await fetch(`/api/users/${id}`).then((r) => r.json());
return UserSchema.parse(data);
}
</eliminating_any>
<result_types>
Exceptions hide errors from types. Result makes them explicit.
type Result<T, E = Error> =
| { readonly ok: true; readonly value: T }
| { readonly ok: false; readonly error: E };
type UserError =
| { readonly type: "not-found"; readonly id: string }
| { readonly type: "network"; readonly message: string };
async function getUser(id: string): Promise<Result<User, UserError>> {
try {
const response = await fetch(`/api/users/${id}`);
if (response.status === 404)
return { ok: false, error: { type: "not-found", id } };
if (!response.ok)
return {
ok: false,
error: { type: "network", message: response.statusText },
};
return { ok: true, value: await response.json() };
} catch (e) {
return { ok: false, error: { type: "network", message: String(e) } };
}
}
// Caller must handle
const result = await getUser(id);
if (!result.ok) {
switch (result.error.type) {
case "not-found":
return showNotFound(result.error.id);
case "network":
return showError(result.error.message);
}
}
return renderUser(result.value);
See result-pattern.md for utilities (map, flatMap, combine).
</result_types>
<discriminated_unions>
Prevent illegal state combinations.
// ❌ Allows { status: 'loading', data: user, error: 'Failed' }
type Request = { status: 'idle'|'loading'|'success'|'error'; data?: User; error?: string; };
// ✅ Only valid states
type RequestState =
| { readonly status: 'idle' }
| { readonly status: 'loading' }
| { readonly status: 'success'; readonly data: User }
| { readonly status: 'error'; readonly error: string };
function render(state: RequestState): JSX.Element {
switch (state.status) {
case 'idle': return <div>Ready</div>;
case 'loading': return <div>Loading...</div>;
case 'success': return <div>{state.data.name}</div>;
case 'error': return <div>Error: {state.error}</div>;
default: return assertNever(state);
}
}
function assertNever(value: never): never {
throw new Error(`Unhandled: ${JSON.stringify(value)}`);
}
</discriminated_unions>
<branded_types>
Prevent mixing incompatible primitives.
declare const __brand: unique symbol;
type Brand<T, B extends string> = T & { readonly [__brand]: B };
type UserId = Brand<string, "UserId">;
type ProductId = Brand<string, "ProductId">;
function createUserId(value: string): UserId {
if (!/^user-\d+$/.test(value)) throw new TypeError(`Invalid: ${value}`);
return value as UserId;
}
const userId = createUserId("user-123");
// getUser(productId); // ❌ Type error
getUser(userId); // ✅ Works
Security:
type SanitizedHtml = Brand<string, "SanitizedHtml">;
function sanitize(raw: string): SanitizedHtml {
return escapeHtml(raw) as SanitizedHtml;
}
function render(html: SanitizedHtml): void {
element.innerHTML = html; // Type proves sanitization
}
See branded-types.md for advanced patterns.
</branded_types>
<resource_management>
using for automatic cleanup (TS 5.2+):
class DatabaseConnection implements Disposable {
[Symbol.dispose]() {
this.close();
}
}
function query() {
using conn = new DatabaseConnection();
return conn.query("SELECT * FROM users");
} // Automatically closed
async function asyncWork() {
await using resource = new AsyncResource();
} // Disposed with await
Use for: connections, file handles, locks, transactions.
</resource_management>
<satisfies_operator>
Validate type without widening (TS 4.9+):
const config = {
port: 3000,
host: "localhost",
} satisfies Record<string, string | number>;
config.port; // number (not string | number)
const routes = {
home: "/",
user: "/user/:id",
} as const satisfies Record<string, string>;
type HomeRoute = typeof routes.home; // '/'
</satisfies_operator>
<const_type_parameters>
Preserve literals through generics (TS 5.0+):
function makeTuple<const T extends readonly unknown[]>(...args: T): T {
return args;
}
const result = makeTuple("a", "b", "c"); // ['a', 'b', 'c'] not string[]
</const_type_parameters>
<inferred_predicates>
TS 5.5+ auto-infers type predicates:
function isString(x: unknown) {
return typeof x === "string";
}
// Inferred: (x: unknown) => x is string
const strings = values.filter(isString); // string[]
</inferred_predicates>
<template_literals>
Pattern matching at type level:
type Route = `/${string}`;
type ApiRoute = `/api/v${number}/${string}`;
type ExtractParams<T extends string> =
T extends `${string}:${infer P}/${infer R}`
? P | ExtractParams<`/${R}`>
: T extends `${string}:${infer P}`
? P
: never;
type Params = ExtractParams<"/user/:id/post/:postId">; // 'id' | 'postId'
See modern-features.md for TS 5.5-5.8.
</template_literals>
Schema = runtime validation + TypeScript type.
<zod_core>
import { z } from "zod";
const UserSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
name: z.string().min(1).max(100),
});
type User = z.infer<typeof UserSchema>;
// safeParse preferred
const result = UserSchema.safeParse(data);
if (!result.success) {
console.error(result.error.issues);
return;
}
const user = result.data;
</zod_core>
<zod_patterns>
Discriminated unions (preferred over z.union):
const ApiResponse = z.discriminatedUnion("type", [
z.object({ type: z.literal("success"), data: z.unknown() }),
z.object({ type: z.literal("error"), code: z.string(), message: z.string() }),
]);
Environment variables:
const EnvSchema = z.object({
NODE_ENV: z
.enum(["development", "production", "test"])
.default("development"),
DATABASE_URL: z.string().url(),
PORT: z.coerce.number().int().positive().default(3000),
});
const env = EnvSchema.parse(process.env);
API validation (Hono):
import { zValidator } from "@hono/zod-validator";
app.post("/users", zValidator("json", UserSchema), (c) => {
const user = c.req.valid("json");
return c.json(user);
});
See:
</zod_patterns>
// User-defined
function isString(v: unknown): v is string {
return typeof v === "string";
}
// Assertion
function assertString(v: unknown): asserts v is string {
if (typeof v !== "string") throw new TypeError("Expected string");
}
// With noUncheckedIndexedAccess
const users: User[] = getUsers();
const first = users[0]; // User | undefined
if (first !== undefined) processUser(first);
See advanced-types.md for utilities.
Types show structure. TSDoc shows intent. Critical for AI agents.
/**
* Authenticates user and returns session token.
* @param credentials - User login credentials
* @returns Session token valid for 24 hours
* @throws {AuthenticationError} Invalid credentials
* @example
* const token = await authenticate({ email, password });
*/
export async function authenticate(
credentials: Credentials
): Promise<SessionToken>;
Document: all exports, parameters with constraints, thrown errors, non-obvious returns.
See tsdoc-patterns.md for comprehensive guide.
<rules>ALWAYS:
import type { User } from './types'assertNeversatisfies for literal inferenceusing for resources with cleanupNEVER:
any (use unknown + guards)@ts-ignore (fix types or document)! (use guards)PREFER:
Type Patterns:
Modern Features:
Zod:
TSDoc:
Examples:
Activates when the user asks about AI prompts, needs prompt templates, wants to search for prompts, or mentions prompts.chat. Use for discovering, retrieving, and improving prompts.
Search, retrieve, and install Agent Skills from the prompts.chat registry using MCP tools. Use when the user asks to find skills, browse skill catalogs, install a skill for Claude, or extend Claude's capabilities with reusable AI agent components.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.