Claude Code Plugins

Community-maintained marketplace

Feedback

Must always be enabled when writing/reviewing TypeScript code.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name typescript
description Must always be enabled when writing/reviewing TypeScript code.
## Core Philosophy: Type-Level Verification

LLM-generated code faces inherent challenges with E2E testing and runtime verification. Compensate by maximizing compile-time verification through:

  • Algebraic data types (discriminated unions, exhaustive pattern matching)
  • Strict type constraints that make invalid states unrepresentable
  • Type-level proofs over runtime assertions

Goal: If it type-checks, it works. Shift as many bugs as possible from runtime to compile-time.

## Type Assertions: Banned by Default

Rule: as Type Assertions are Prohibited

Rationale: Type assertions bypass TypeScript's type system and introduce type unsoundness. They are frequently misused to silence legitimate type errors.

Policy:

  • Never use as to resolve type errors
  • Never use as any or as unknown as X
  • ⚠️ Rare exceptions: Compiler limitations (e.g., specific generic inference bugs)
    • If you encounter such cases, leave the type error unresolved
    • Escalate to user with explanation: "Type error at path/to/file.ts:123 - requires manual review for potential as usage"

Why you cannot judge appropriately: As an LLM, you lack the contextual understanding to determine if a type assertion is truly necessary vs. masking a real type error. When in doubt, preserve type safety.

Alternative: Fix the Root Cause

Instead of as, address the underlying type issue:

  • Refine function signatures
  • Use type guards (if (typeof x === 'string'))
  • Employ discriminated unions
  • Add generic constraints
## Strict Typing Patterns

Prefer as const satisfies Over Loose Annotations

Problem with loose typing:

const config: Config = {
  mode: 'development',  // Type widened to string
  port: 3000
}
// config.mode is string, not 'development' | 'production'

Solution - strict typing with as const satisfies:

const config = {
  mode: 'development',
  port: 3000
} as const satisfies Config
// config.mode is exactly 'development' (literal type preserved)

Benefits:

  • Preserves literal types
  • Catches typos at definition site
  • Enables exhaustive checking in consumers
  • No type widening

Application:

  • Configuration objects
  • Constant lookup tables
  • Route definitions
  • Action type constants

Avoid Explicit Type Annotations When Inference Suffices

// ❌ Redundant annotation
const result: number = calculateTotal(items)

// ✅ Let TypeScript infer
const result = calculateTotal(items)

Use annotations when:

  • Constraining function parameters
  • Enforcing strict object shapes (as const satisfies)
  • Documenting public API boundaries
## External Data: Never Trust, Always Validate

Rule: No any for External Data

Sources requiring validation:

  • API responses (fetch, axios, etc.)
  • JSON.parse() results
  • LocalStorage/SessionStorage reads
  • FormData / user input
  • Environment variables
  • File system reads

Strategy 1: Type-Safe API Clients (Preferred)

Check for generated type definitions first:

  • Hono: hono/client with type inference
  • orval: OpenAPI-generated types and hooks
  • tRPC: End-to-end type safety
  • GraphQL Code Generator: Typed queries

Example (Hono client):

import { hc } from 'hono/client'
import type { AppType } from './server'

const client = hc<AppType>('/api')
const response = await client.users.$get()
// response is fully typed from server definition

Action: Review existing codebase for established patterns. Most projects already have type-safe API layers.

Strategy 2: Runtime Validation Libraries

When type generation is unavailable, use schema validation:

Preference order:

  1. Existing project dependency (check package.json)
  2. valibot (lightweight, install if needed: pnpm add valibot)
  3. zod (popular, larger bundle)

Example (valibot):

import * as v from 'valibot'

const UserSchema = v.object({
  id: v.number(),
  name: v.string(),
  role: v.union([v.literal('admin'), v.literal('user')])
})

// Parse and validate
const response = await fetch('/api/user')
const data = await response.json()
const user = v.parse(UserSchema, data)  // Throws if invalid
// user is now typed as { id: number, name: string, role: 'admin' | 'user' }

Example (JSON.parse):

// ❌ Unsafe
const data = JSON.parse(localStorage.getItem('config')!)

// ✅ Validated
const raw = localStorage.getItem('config')
if (raw) {
  const data = v.parse(ConfigSchema, JSON.parse(raw))
}

Never Skip Validation

Even if "you know" the shape, external data can change:

  • API contracts evolve
  • Users manipulate localStorage
  • Third-party services have bugs

Type safety = static types + runtime validation

## General Best Practices

Prefer Arrow Functions Over Function Declarations

Rule: Use arrow functions (=>) instead of function keyword for consistency and lexical scoping benefits.

Rationale:

  • Consistent lexical this binding (no context confusion)
  • More concise syntax
  • Better integration with modern TypeScript patterns
  • Prevents accidental hoisting-related bugs
// ❌ Function declaration
function calculateTotal(items: Item[]): number {
  return items.reduce((sum, item) => sum + item.price, 0)
}

// ✅ Arrow function
const calculateTotal = (items: Item[]): number => {
  return items.reduce((sum, item) => sum + item.price, 0)
}

// ✅ Concise form (single expression)
const calculateTotal = (items: Item[]): number =>
  items.reduce((sum, item) => sum + item.price, 0)

Exception: When hoisting is genuinely required (rare), document the reason.

Discriminated Unions for State

type LoadingState<T> =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; error: Error }

const render = (state: LoadingState<User>) => {
  switch (state.status) {
    case 'idle':
      return 'Not started'
    case 'loading':
      return 'Loading...'
    case 'success':
      return state.data.name  // data is available
    case 'error':
      return state.error.message  // error is available
  }
}

Benefits: Impossible to access data when status is 'error'.

Exhaustiveness Checking

const assertNever = (x: never): never => {
  throw new Error(`Unexpected value: ${x}`)
}

switch (state.status) {
  case 'idle':
  case 'loading':
  case 'success':
  case 'error':
    return
  default:
    assertNever(state)  // Compile error if cases are missing
}

Avoid Optional Properties for State

// ❌ Ambiguous state
type User = {
  data?: UserData
  error?: Error
}
// What if both are defined? Neither?

// ✅ Explicit state
type User =
  | { status: 'success'; data: UserData }
  | { status: 'error'; error: Error }

Use unknown Over any for Truly Unknown Types

// ❌ Disables all type checking
const process = (data: any) => {
  return data.foo.bar  // No errors, runtime explosion
}

// ✅ Forces validation
const process = (data: unknown) => {
  if (typeof data === 'object' && data !== null && 'foo' in data) {
    // Narrow the type before use
  }
}

Readonly by Default

// Prevent accidental mutations
type Config = {
  readonly apiUrl: string
  readonly timeout: number
}

// For arrays
const items = ['a', 'b'] as const

Avoid Type-Level Gymnastics

If type definitions become incomprehensible, simplify the design:

  • Complex conditional types often indicate over-abstraction
  • Prefer explicit discriminated unions over heavily generic types
  • Maintainability > cleverness
## When Type Errors Cannot Be Resolved

If you encounter legitimate type errors you cannot fix without as:

  1. Leave the error in place
  2. Document the issue:
    // TODO: Type error at line X - potential TypeScript limitation
    // Requires manual review before using type assertion
    const result = someComplexOperation()  // Type error here
    
  3. Notify the user: "Type error remains at src/module.ts:45 - escalated for review"

Do not:

  • Silently add as assertions
  • Use any to bypass the error
  • Restructure correct code to satisfy incorrect types