AGENT SKILL

typescript-patterns

TypeScript best practices and patterns for type-safe, maintainable code. Covers discriminated unions, generics, const assertions, type narrowing, and common anti-patterns. Extends (does not override) project tsconfig. EN triggers: /typescript, TypeScript patterns, TS best practices, type safety, discriminated unions, generics, type narrowing. PT triggers: /typescript, padrões TypeScript, boas práticas TS, segurança de tipos, unions discriminadas, genéricos. Do NOT use for: JavaScript-only projects, runtime performance, or replacing project tsconfig rules.

License 许可
CC-BY-4.0
最近更新
2026-07-29
Stars
★ 1
Forks
0

01 前言 Front Matter

name
typescript-patterns
license
CC-BY-4.0
description
TypeScript best practices and patterns for type-safe, maintainable code. Covers discriminated unions, generics, const assertions, type narrowing, and common anti-patterns. Extends (does not override) project tsconfig. EN triggers: /typescript, TypeScript patterns, TS best practices, type safety, discriminated unions, generics, type narrowing. PT triggers: /typescript, padrões TypeScript, boas práticas TS, segurança de tipos, unions discriminadas, genéricos. Do NOT use for: JavaScript-only projects, runtime performance, or replacing project tsconfig rules.

02 资源文件 1

03 SKILL.md 正文

TypeScript Patterns

Overview

This skill provides TypeScript patterns that produce type-safe, maintainable code. It extends your project's tsconfig — not overrides it. When in conflict, the project tsconfig wins.

All patterns assume strict mode is enabled (strict: true in tsconfig) and that noUncheckedIndexedAccess, exactOptionalPropertyTypes, and other strict-family flags are on when available. The patterns work with the strictest reasonable settings — they never rely on lax compiler options.

When to Use

  • Writing new TypeScript code
  • Refactoring existing code for stronger type safety
  • Reviewing code for type-level issues
  • Eliminating any and as casts from a codebase
  • Designing shared types, APIs, or generic utilities
  • Deciding between structurally equivalent alternatives (union vs enum, optional vs | undefined)

Core Patterns

1. Discriminated Unions

Use a kind (or type) field to create a union that the compiler can exhaustively narrow.

type Shape =
  | { kind: 'circle'; radius: number }
  | { kind: 'rectangle'; width: number; height: number }
  | { kind: 'triangle'; base: number; height: number };

function area(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2;
    case 'rectangle':
      return shape.width * shape.height;
    case 'triangle':
      return (shape.base * shape.height) / 2;
  }
}

Exhaustiveness check — add a never-asserting helper so adding a new variant produces a compile error, not a silent fallthrough:

function assertNever(value: never): never {
  throw new Error(`Unhandled case: ${JSON.stringify(value)}`);
}

function area(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2;
    case 'rectangle':
      return shape.width * shape.height;
    case 'triangle':
      return (shape.base * shape.height) / 2;
    default:
      return assertNever(shape);
  }
}

Prefer string literal unions over boolean flags for mutually exclusive states. { status: 'loading' } | { status: 'success'; data: T } | { status: 'error'; error: Error } is clearer than { loading: boolean; data?: T; error?: Error } and eliminates impossible states.

2. Generics with Constraints

Always constrain generic parameters to the shape you actually depend on. A bare <T> gives no information to the caller or the compiler.

// BAD: T could be anything — no way to access .id
function first<T>(items: T[]): T {
  return items[0];
}

// GOOD: constraint communicates intent and enables property access
function first<T extends { id: string }>(items: T[]): T {
  return items[0];
}

Keep generics shallow. If you need four or more type parameters, reconsider the abstraction — it may be too general or the responsibilities should be split.

// BAD: too many unconstrained params
function map<S, T, U, V>(a: S[], b: T[], fn: (a: S, b: T) => [U, V]): [U[], V[]] { /* ... */ }

// GOOD: compose simpler functions instead

Use extends to narrow, infer to extract (in conditional types), and defaults (<T = unknown>) to make call sites ergonomic.

3. Const Assertions

as const tells the compiler to infer the narrowest possible type — literal values and readonly tuples/objects.

// Without as const: widened to string[]
const COLORS = ['red', 'green', 'blue'];
// type: string[]

// With as const: narrowed to readonly tuple of literals
const COLORS = ['red', 'green', 'blue'] as const;
// type: readonly ["red", "green", "blue"]

type Color = (typeof COLORS)[number];
// type Color = "red" | "green" | "blue"

Use as const for:

  • Enumerations (prefer over enum)
  • Configuration objects that drive conditional types
  • Literal-tuple derived union types
  • const objects used as lookup tables
const STATUS_MAP = {
  draft: 'Draft',
  published: 'Published',
  archived: 'Archived',
} as const;

type Status = keyof typeof STATUS_MAP; // "draft" | "published" | "archived"

4. Type Narrowing

Narrow types explicitly. The compiler can follow typeof, instanceof, in, and custom type guards — use them instead of casting.

function process(input: string | number | Date) {
  if (typeof input === 'string') {
    return input.toUpperCase();       // input: string
  }
  if (typeof input === 'number') {
    return input.toFixed(2);          // input: number
  }
  return input.toISOString();         // input: Date (by elimination)
}

Custom type guards make complex narrowing reusable:

function isUser(obj: unknown): obj is User {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    'id' in obj &&
    'email' in obj
  );
}

function process(input: unknown) {
  if (isUser(input)) {
    console.log(input.email); // input: User
  }
}

in operator for discriminated properties when you don't control the union:

type Response = SuccessResponse | ErrorResponse;

if ('data' in response) {
  console.log(response.data); // response: SuccessResponse
}

Use unknown at system boundaries (API responses, parsed JSON, message queues). Narrow to a known type before use — never cast.

function parseApiResponse(raw: unknown): User[] {
  if (!Array.isArray(raw)) throw new Error('Expected array');
  return raw.map((item) => {
    if (!isUser(item)) throw new Error('Invalid user shape');
    return item; // item: User
  });
}

5. Template Literal Types

Use template literal types for string pattern matching — route params, event names, CSS-in-JS, and more.

type EventName = `${string}:${string}`;
// "click:button", "key:enter", "submit:form"

type CSSUnit = `${number}${'px' | 'rem' | 'em' | '%'}`;
// "16px", "1.5rem", "80%"

type RouteParams<T extends string> =
  T extends `${string}:${infer Param}/${infer Rest}`
    ? Param | RouteParams<Rest>
    : T extends `${string}:${infer Param}`
      ? Param
      : never;

type Params = RouteParams<'/users/:userId/posts/:postId'>;
// "userId" | "postId"

Combine with Capitalize, Uncapitalize, Uppercase, and Lowercase intrinsic types for string transformation:

type Getters<T extends string> = `get${Capitalize<T>}`;
type UserGetters = Getters<'name' | 'email'>; // "getName" | "getEmail"

6. Utility Types

Know the standard library before writing custom mapped types.

Utility Use when
Partial<T> All properties optional — e.g., update DTOs
Required<T> All properties required — remove optionality
Pick<T, K> Select a subset of keys — API response shaping
Omit<T, K> Exclude specific keys — public vs internal types
Record<K, V> Map a union of keys to a single value type
Readonly<T> Prevent mutation — function parameters, config
NonNullable<T> Strip null and undefined from a union
ReturnType<T> Extract the return type of a function type
Awaited<T> Unwrap a Promise type recursively
interface User {
  id: string;
  email: string;
  passwordHash: string;
  createdAt: Date;
}

type PublicUser = Omit<User, 'passwordHash'>;
type UpdateUserDTO = Partial<Pick<User, 'email'>>;

type Status = 'active' | 'inactive';
const statusLabels: Record<Status, string> = {
  active: 'Active',
  inactive: 'Inactive',
};

Anti-Patterns to Avoid

any — Use unknown Then Narrow

any disables type checking entirely. Use unknown at boundaries and narrow before use.

// BAD
function handle(data: any) {
  console.log(data.email); // no error, no safety
}

// GOOD
function handle(data: unknown) {
  if (isUser(data)) {
    console.log(data.email); // safe
  }
}

Type Assertions (as Type) — Prefer Type Guards

Assertions silence the compiler but don't validate at runtime. A type guard narrows through actual checks.

// BAD: assertion — no runtime check
const user = response.data as User;

// GOOD: type guard — validates at runtime
if (!isUser(response.data)) throw new Error('Unexpected shape');
const user = response.data; // user: User

The one acceptable use of as is as const for literal inference (see Core Pattern 3).

Overly Complex Generics

If a generic has four or more type parameters, or if the conditional types are three levels deep, it's probably over-designed. Prefer simpler, more concrete types unless the abstraction pays for itself across at least three call sites.

enum — Prefer as const Objects or String Literal Unions

TypeScript's enum is a runtime construct with surprising behavior (reverse mapping, numeric auto-increment, const-enum pitfalls). String literal unions and as const objects are simpler and more predictable.

// BAD: enum
enum Status {
  Active = 'active',
  Inactive = 'inactive',
}

// GOOD: string literal union
type Status = 'active' | 'inactive';

// GOOD: as const object (when you need runtime access to values)
const Status = {
  Active: 'active',
  Inactive: 'inactive',
} as const;
type Status = (typeof Status)[keyof typeof Status];

Optional Chaining Abuse

?. everywhere makes error sources invisible. Use it intentionally at boundaries where null/undefined is expected, and prefer early returns with explicit null checks inside function bodies.

// BAD: silent failure — if anything is missing, result is undefined with no trace
const result = data?.nested?.deeply?.value?.toString()?.toUpperCase();

// GOOD: explicit null checks with clear error boundaries
function format(data: Data): string {
  if (!data.nested?.deeply?.value) {
    throw new Error('Missing deeply nested value');
  }
  return data.nested.deeply.value.toString().toUpperCase();
}

Verification

After applying TypeScript patterns to new or refactored code:

  • [ ] Zero any in new code (or every any is justified by a comment explaining why unknown+narrowing wasn't feasible)
  • [ ] Type assertions (as Type) are replaced by type guards or explicit validation
  • [ ] Discriminated unions have an exhaustiveness check (assertNever in default branch)
  • [ ] Generics have meaningful constraints (extends ...) — no bare <T>
  • [ ] enum declarations are avoided in favor of string unions or as const objects
  • [ ] unknown is used at system boundaries (API responses, parsed JSON, message queues) and narrowed before use
  • [ ] Optional chaining is used intentionally at boundaries, not as a blanket pattern throughout functions
  • [ ] The code compiles with strict: true and no @ts-ignore or @ts-expect-error suppression comments

04 同仓库更多 Skills

runecraftai
runecraftai/arcanum

using-agent-skills

Discovers and invokes the right @runecraft/spells skill for the current task. Use when starting a session, when a request is ambiguous about which workflow to apply, or when you need to pick among the 13-skill Arcanum catalog. EN triggers: /skill, which skill, pick a skill, what skill should I use, skill discovery, meta skill. PT triggers: qual skill, qual habilidade, descobrir skill, qual fluxo usar. Do NOT use for: actual implementation work (route to the specific skill instead), unrelated standalone tasks outside the Arcanum catalog, or runtime-specific configuration (e.g., opencode.json / Claude Code plugin paths).

Skill与智能体开发
★ 1
runecraftai
runecraftai/arcanum

test-driven-development

Drives development with tests — fail first, then make them pass, then refactor. Use when implementing any logic, fixing any bug, or changing any behavior. EN triggers: /test, write tests first, TDD, prove it, 80/15/5 pyramid, Beyonce Rule. PT triggers: /teste, testes primeiro, provar com teste, pirâmide 80/15/5. Do NOT use for: production incident triage (use /debug), or cosmetic-only changes with no behavioral surface.

代码审查测试与质量
★ 1
runecraftai
runecraftai/arcanum

spec-driven

Feature planning and implementation with 4 adaptive phases — Specify, Design, Tasks, Execute. Auto-sizes depth by complexity. Creates atomic tasks with verification criteria, atomic git commits, and requirement traceability. Features an independent Verifier (author != verifier, evidence-or-zero), persistent decision log (STATE.md), and test-coverage-matrix-driven tests, plus a self-improving lessons layer that turns verification failures into reusable project-local guidance. Stack-agnostic. Use when planning features (requirements, design, task breakdown), implementing with verification and atomic commits, or validating/verifying an implementation against a spec. Triggers (PT/EN): "specify feature", "vamos especificar", "discutir feature", "discutir este caso", "design", "design da feature", "tarefas", "quebrar em tarefas", "implementar", "build", "validar", "verify work", "UAT", "validar implementação", "record decision", "pause work", "pausar trabalho", "resume work", "retomar trabalho". Do NOT use for architecture decomposition analysis (use architecture skills) or technical design docs (treat as Complex scope: discuss gray areas + architecture-focused design).

★ 1
runecraftai
runecraftai/arcanum

skill-forge

Designs, authors, validates, and optimizes new Agent Skills from scratch. Use when creating a new skill, packaging a workflow as a skill, improving a skill's triggering accuracy, or evaluating whether a skill improves output quality. Covers the full lifecycle: DISCOVER, DESIGN, AUTHOR, VALIDATE, OPTIMIZE, DELIVER. Produces skills that follow the open SKILL.md format (YAML frontmatter + Markdown body, progressive disclosure, validation loops). EN triggers: /forge, create a skill, write a skill, build a skill, new skill, author a skill, package this as a skill, validate a skill, improve skill triggering, optimize skill description, evaluate skill quality. PT triggers: /forge, criar uma skill, escrever uma skill, construir uma skill, nova skill, empacotar como skill, validar skill, melhorar disparo, otimizar descrição de skill. Do NOT use for: discovering which existing skill to apply (use a dispatch/meta skill), planning feature implementation, or designing software architecture.

视觉设计Skill与智能体开发
★ 1
runecraftai
runecraftai/arcanum

shipping-and-launch

Prepares production launches with a pre-launch checklist, staged rollout, monitoring, and a rollback strategy — plus feature flag lifecycle (off → internal → beta → GA). Use when deploying a feature to production for the first time, releasing a significant change, or planning a rollout. EN triggers: /ship, pre-launch checklist, staged rollout, feature flag, rollback plan, monitoring. PT triggers: /shippar, checklist pré-lançamento, rollout gradual, feature flag, plano de rollback. Do NOT use for: pre-production development work, or one-off internal tools with no user exposure.

★ 1
runecraftai
runecraftai/arcanum

security-and-hardening

Hardens code against vulnerabilities using the OWASP Top 10 and a three-tier boundary system (input, processing, output). Use when handling user input, authentication, data storage, or external integrations. EN triggers: /security, security audit, OWASP, three-tier boundary, least privilege, input validation. PT triggers: /segurança, auditoria de segurança, OWASP, privilégio mínimo, validar entrada. Do NOT use for: non-security performance work (use /review's performance axis), or dependency updates that have no security exposure.

数据分析师
★ 1