agentleFS
bilals2008bilals2008/bilal-skills/skills/typescript-best-practices/SKILL.md

typescript-best-practices

Skill0 starsChanged 4 months agoMITView on GitHub

TypeScript code quality and performance optimization guidelines for writing type-safe, maintainable code. Use when writing new TypeScript files, reviewing existing code, refactoring to improve type safety, optimizing build performance, or setting up TypeScript configuration. Triggers on tasks involving TypeScript, tsconfig, type definitions, generics, or type system design.

77 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use setup-pstack, poteto-help and 10 more.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • bnemaSkill

    setup-pstack

    Configure which Pi models pstack uses per role. Detects available provider/model specs and writes pstack's user configuration. Use for /setup-pstack, "configure pstack models", or changing pstack model choices.

    Also in 22 projects that use this

  • ianclearySkill

    poteto-help

    Guides users through pstack setup, /poteto-mode, and picking the skill, playbook, or principle for a task. Type /poteto-help with a question.

    Also in 12 projects that use this

  • eryzerzSkill

    make-bot-ui

    Use when building a custom UI (page, dashboard, buttons) that should wake a Grok Bot over a webhook, when the user must provide a webhook sender key, or when exposing that UI on Tailscale.

    Also in 14 projects that use this

  • ianclearySkill

    principle-explain-the-number

    Apply before you trust, report, or act on a number you measured: a speedup, a regression, a throughput, a latency, or an eval result. Find what limits it, and rule out that it measured something other than the work you think.

    Also in 20 projects that use this

  • tristanphvnSkill

    ketchup-video

    Catch the user up like /keel:ketchup, with a short silent video of slides for what happened and the waiting calls as text under it. Use for /keel:ketchup-video.

    Also in 8 projects that use this

  • instructureSkill

    ketchup

    Rebuild context after /clear by analyzing uncommitted changes across Canvas LMS and all plugin repositories

    Also in 17 projects that use this

The file

We split this skill into the rules it gives an agent

11 rules. 0 are common, 5 are distinctive, and 6 are very specific to this project.

  • 0

    Common

    Most of the 26 edited versions we read keep these.

  • 5

    Distinctive

    This author's own choices. Worth a look.

  • 6

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    When the user explicitly asks for non-TypeScript solutions

    line 36
  2. Distinctive:

    Before writing code, ensure:

    line 42
  3. Very specific:

    Import @total-typescript/ts-reset for better array and Promise type inference.

    line 194
  4. Very specific:

    Use @ts-expect-error instead of @ts-ignore when you must suppress an error.

    line 268
  5. Distinctive:

    Keep functions small and focused.

    line 269
  6. Very specific:

    Prefer interface over type for public API shapes.

    line 270
  7. Very specific:

    Use const assertions for constant tuples and configurations.

    line 271
  8. Very specific:

    Write unit tests for your type definitions using expectTypeOf from vitest or ts-expect.

    line 273
  9. Distinctive:

    Complex generic signatures: If a generic type signature is hard to read, it is probably too complex.

    line 280
  10. Very specific:

    Defining types in the same file as implementation: Keep type definitions in separate .types.ts files for better organization.

    line 282
  11. Distinctive:

    When this skill is triggered, the agent should produce:

    line 286

“Common” is measured: kept by at least 6 in 10 of the 26 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

TypeScript Best Practices

Act as a TypeScript expert advisor. Provide guidance on writing type-safe, performant, and maintainable TypeScript code following community best practices and the official TypeScript team recommendations.

Purpose

This skill helps AI agents produce high-quality TypeScript code that leverages the type system effectively while avoiding common pitfalls. It covers type system design, configuration optimization, performance patterns, and project structure.

When to Use

  • Writing new TypeScript files or modules
  • Reviewing existing TypeScript code for type safety issues
  • Refactoring JavaScript to TypeScript
  • Configuring tsconfig.json for a project
  • Designing type definitions or generic APIs
  • Optimizing TypeScript build and compilation performance
  • Debugging complex type errors
  • Setting up strict mode and linting integration
  • Working with union/discriminated union types
  • Implementing advanced patterns (branded types, type guards, assertion functions)

When NOT to Use

  • Writing pure JavaScript code that will not be migrated to TypeScript
  • Projects using a different type system (Flow, ReasonML)
  • Simple type annotations that are obvious (the agent should already handle these)
  • When the user explicitly asks for non-TypeScript solutions

Instructions

1. Configuration First

Before writing code, ensure:

  • strict: true is enabled in tsconfig.json. Without it, the type system is significantly weaker.
  • noUncheckedIndexedAccess: true for safer object access.
  • exactOptionalPropertyTypes: true for stricter optional property handling.
  • verbatimModuleSyntax: true for consistent module syntax.

2. Type System Design

Prefer interfaces over type aliases for object shapes

Interfaces are more extensible and produce clearer error messages. Use type for unions, intersections, mapped types, and utility patterns.

// Prefer
interface User {
  id: string;
  name: string;
}

// Use type for
type Status = 'active' | 'inactive' | 'pending';
type DeepPartial<T> = { [K in keyof T]?: DeepPartial<T[K]> };

Use branded types for domain primitives

Prevent mixing up values that share the same underlying type but represent different concepts.

type Brand<T, B> = T & { __brand: B };
type UserId = Brand<string, 'UserId'>;
type OrderId = Brand<string, 'OrderId'>;

function getUser(id: UserId): User { /* ... */ }
function getOrder(id: OrderId): Order { /* ... */ }

// TypeScript prevents: getUser(orderId)

Leverage discriminated unions for state management

Model states explicitly so invalid states are unrepresentable.

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

function renderState(state: RequestState<User>) {
  switch (state.status) {
    case 'idle': return null;
    case 'loading': return <Spinner />;
    case 'success': return <UserProfile user={state.data} />;
    case 'error': return <ErrorMessage error={state.error} />;
  }
}

Prefer type inference over explicit annotations

Let TypeScript infer types when the type is obvious from context. Add explicit annotations at module boundaries (function signatures, exported API surface).

// Good: inferred
const items = [1, 2, 3];

// Good: explicit at boundary
export function processItems(items: number[]): Result[] { /* ... */ }

// Unnecessary
const name: string = 'hello';

3. Generics and Utility Types

Constrain generics with the minimal required shape

// Instead of
function clone<T>(obj: T): T { return JSON.parse(JSON.stringify(obj)); }

// Prefer
function clone<T extends Record<string, unknown>>(obj: T): T { return JSON.parse(JSON.stringify(obj)); }

Use satisfies for type validation without widening

const palette = {
  red: [255, 0, 0],
  green: '#00ff00',
  blue: [0, 0, 255],
} satisfies Record<string, string | number[]>;

// palette.red is number[] (not string | number[])
// palette.green is string (not string | number[])

4. Strictness Patterns

Use as const for literal types and readonly tuples

const COLORS = ['red', 'green', 'blue'] as const;
type Color = (typeof COLORS)[number]; // 'red' | 'green' | 'blue'

Prefer unknown over any

unknown forces type checking before use. Reserve any for migration scenarios only.

// Prefer
function parseJSON(input: string): unknown {
  return JSON.parse(input);
}

// Avoid
function parseJSON(input: string): any {
  return JSON.parse(input);
}

Use type guards and assertion functions

function isError(value: unknown): value is Error {
  return value instanceof Error;
}

function assertString(value: unknown): asserts value is string {
  if (typeof value !== 'string') {
    throw new TypeError('Expected string');
  }
}

5. Performance Optimization

Prefer interfaces for object types

Interfaces are faster for the compiler to check than type aliases due to caching.

Avoid large union types

Break large unions (100+ members) into smaller, composable types. Consider using branded types or enums for finite sets.

Use ts-reset or explicit narrowing

Import @total-typescript/ts-reset for better array and Promise type inference. Otherwise, manually narrow with explicit checks.

Avoid excessive conditional types

Deeply nested conditional types slow down compilation. Prefer mapped types or lookup types when possible.

6. Error Handling

Use Result types for expected failures

type Result<T, E = Error> = { success: true; value: T } | { success: false; error: E };

function divide(a: number, b: number): Result<number> {
  if (b === 0) return { success: false, error: new Error('Division by zero') };
  return { success: true, value: a / b };
}

Use typed try/catch with error handling

try {
  await riskyOperation();
} catch (error) {
  if (error instanceof ApiError) {
    handleApiError(error);
  } else if (error instanceof NetworkError) {
    retry();
  } else {
    throw error;
  }
}

Examples

Before (untyped)

function process(data) {
  return data.map(item => item.value);
}

After (type-safe)

interface DataItem {
  id: string;
  value: number;
}

function process(data: DataItem[]): number[] {
  return data.map(item => item.value);
}

API Response Handling

interface ApiResponse<T> {
  data: T;
  timestamp: string;
  version: number;
}

async function fetchApi<T>(url: string): Promise<ApiResponse<T>> {
  const response = await fetch(url);
  if (!response.ok) throw new ApiError(response.status, await response.text());
  return response.json();
}

Best Practices

  • Enable strict: true in every project. This catches the majority of type errors.
  • Use @ts-expect-error instead of @ts-ignore when you must suppress an error. @ts-expect-error will warn you if the error is no longer relevant, while @ts-ignore silently suppresses forever.
  • Keep functions small and focused. TypeScript's type inference works best with small, pure functions.
  • Prefer interface over type for public API shapes. Interfaces are more extensible and produce better editor tooling.
  • Use const assertions for constant tuples and configurations.
  • Export types that consumers of your library or module need.
  • Write unit tests for your type definitions using expectTypeOf from vitest or ts-expect.

Common Mistakes

  • Using any instead of unknown: any disables type checking entirely. Always prefer unknown and narrow the type before use.
  • Over-using type assertions (as): Type assertions hide type errors. Prefer type guards and constructor functions.
  • Missing strict: true: Without strict mode, TypeScript misses null checks, implicit any, and other common errors.
  • Complex generic signatures: If a generic type signature is hard to read, it is probably too complex. Break it down or use simpler patterns.
  • Ignoring noUncheckedIndexedAccess: Accessing object properties without checking for undefined leads to runtime errors.
  • Defining types in the same file as implementation: Keep type definitions in separate .types.ts files for better organization.

Expected Output

When this skill is triggered, the agent should produce:

  1. Type-safe code with proper annotations at module boundaries
  2. Fully configured tsconfig.json with strict mode enabled
  3. Discriminated unions for state management where applicable
  4. Branded types for domain primitives to prevent type confusion
  5. Proper error handling with typed error variants
  6. Clean, readable generics constrained to the minimum required shape
  7. Inline documentation only where the type signature is non-obvious

Limitations

  • This skill does not cover runtime validation libraries like Zod or Yup (consider installing a separate skill for those).
  • This skill does not cover framework-specific TypeScript patterns for React, Next.js, or Angular.
  • TypeScript compilation performance depends on project size; this skill provides general guidance but cannot fix inherent scalability limits of the TypeScript compiler.
  • Some pattern recommendations may conflict with existing codebase conventions; always respect the project's established style.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.