원클릭으로
typescript-idioms
TypeScript strict mode, type narrowing, Zod validation, vitest, ESLint flat config.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
TypeScript strict mode, type narrowing, Zod validation, vitest, ESLint flat config.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
Structured fault tolerance for coordinator agents. 5-level escalation ladder (Retry → Replace → Skip → Redistribute → Degrade), dead-man timers, degraded completion protocol, and cross-level escalation format. Load when orchestrating agents that may fail.
Structured code review protocol for inspecting code quality against the full rule set. Use when auditing code written by yourself or another agent, during the /audit workflow, or when the user asks for a code review.
Reusable convergence protocol for coordinator agents. Defines the BRIEFING → ITERATE → GATE → CONVERGE loop, context hygiene rules, self-succession protocol, turn budget, and handoff compression. Load when orchestrating multi-iteration workflows.
Pre-flight checklist and post-implementation self-review protocol. Use before generating any code (pre-flight) and after writing code but before verification (self-review) to catch issues early.
MECE task decomposition, file ownership enforcement, DAG-based execution, and safe merge protocol for intra-domain parallel dispatch. The safety invariants that prevent merge chaos when multiple agents write in parallel. Applies recursively at every nesting depth.
Shared protocols for all agents in the multi-agent pipeline: recursive nesting, pre-implementation restatement, parallel dispatch format, and agent definition cascade. Load this skill instead of inlining these protocols in every agent file.
| name | typescript-idioms |
| description | TypeScript strict mode, type narrowing, Zod validation, vitest, ESLint flat config. |
| paths | ["**/*.ts","**/*.tsx","**/tsconfig.json"] |
TypeScript's type system is your documentation, your test, and your specification — all at once. Make the type system encode the invariants of your domain so that invalid states are unrepresentable. Lean into the compiler.
Scope: This file covers TypeScript-specific type system and language idioms. For framework-specific patterns, see the respective idiom skill (Vue, React, Angular, Next.js, Hono). For file layout, see
references/project-structure.md in this skill. For quality commands, seecode-idioms-and-conventions.md. For logging library, see@.agents/skills/logging-implementation/SKILL.md.
Always enable strict mode in tsconfig.json:
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true
}
}
These flags catch the majority of runtime errors at compile time. Never disable them on a per-file basis without a // STRICT-DISABLE: comment explaining the rationale.
unknown over any — always
// ✅ Forces narrowing before use
function parse(data: unknown): User {
if (!isUser(data)) throw new Error('Invalid user shape');
return data;
}
// ❌ Disables the type checker entirely
function parse(data: any): User { return data; }
Use readonly to enforce immutability at compile time
interface TaskState {
readonly id: string;
readonly items: readonly Task[];
}
// For function params that should not be mutated
function process(tasks: readonly Task[]): Summary { ... }
Discriminated unions for type-safe state machines
type AsyncState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error };
// Exhaustive handling — compiler catches missing cases
function render(state: AsyncState<User>): string {
switch (state.status) {
case 'idle': return 'Waiting...';
case 'loading': return 'Loading...';
case 'success': return state.data.name; // data is typed
case 'error': return state.error.message;
}
}
Const assertions for literal types
const ROLES = ['admin', 'editor', 'viewer'] as const;
type Role = typeof ROLES[number]; // 'admin' | 'editor' | 'viewer'
Type narrowing — use type guards instead of as casts
// ✅ Type guard — safe narrowing
function isError(value: unknown): value is Error {
return value instanceof Error;
}
// ❌ Type assertion — bypasses type checker
const err = value as Error;
Never use non-null assertion ! in production code
// ❌ Hides a potential null/undefined bug
const name = user!.profile!.name;
// ✅ Explicit handling
const name = user?.profile?.name ?? 'Anonymous';
satisfies operator for type-checked object literals (TS 4.9+)
// ✅ satisfies: compile-checked against interface, type stays as literal
const config = {
endpoint: '/api/tasks',
retries: 3,
} satisfies ApiConfig;
// config.retries is typed as `3` (literal), not `number` — narrower and safer
Prefer ?? (nullish coalescing) over || for default values
// ✅ Only falls back for null/undefined
const count = input.count ?? 0;
// ❌ Also falls back for 0, '', false
const count = input.count || 0;
Use optional chaining ?. for safe navigation
const city = user?.address?.city;
Distinguish undefined (absence) from null (explicit empty)
undefined for optional fieldsnull only when you need to represent "intentionally empty" on the wire (JSON APIs)For general async patterns (when to use concurrency), see
core-design-principles.md§ Concurrency. This section covers TypeScript-specific async idioms.
Always await or handle returned Promises — no floating promises
// ❌ Fire-and-forget — errors are silently swallowed
sendEmail(user);
// ✅ Awaited
await sendEmail(user);
// ✅ Intentionally fire-and-forget needs explicit void annotation
void sendEmail(user); // still logs errors internally
Use Promise.all for concurrent independent operations
// ✅ Concurrent — total time = max(individual times)
const [user, tasks] = await Promise.all([getUser(id), getTasks(id)]);
// ❌ Sequential — total time = sum of individual times
const user = await getUser(id);
const tasks = await getTasks(id);
Use Promise.allSettled when partial failure is acceptable
const results = await Promise.allSettled(notifications.map(send));
const failed = results.filter(r => r.status === 'rejected');
Never mix async/await with raw .then()/.catch() chains in the same function
All data crossing a system boundary must be validated at runtime, not just typed.
import { z } from 'zod';
// Define schema as the single source of truth
const CreateTaskSchema = z.object({
title: z.string().min(1).max(200),
priority: z.enum(['low', 'medium', 'high']),
dueDate: z.string().datetime().optional(),
});
// Infer the TypeScript type from the schema — no duplication
type CreateTaskRequest = z.infer<typeof CreateTaskSchema>;
// Validate at the API boundary
function parseCreateTask(body: unknown): CreateTaskRequest {
return CreateTaskSchema.parse(body); // throws ZodError on invalid input
}
zod for runtime schema validation at API ingress and external API egressas operator as a substitute for runtime validationAll outbound HTTP calls MUST go through the project's single, shared API client utility.
Do not call fetch() or axios() directly in feature code. Route every request through the centralized client (e.g., apiFetch, apiClient, or equivalent).
Why this matters:
// ❌ Anti-pattern — bypass: no auth header, no correlation-ID, no logging
const res = await fetch('/api/tasks');
// ✅ Correct — use the shared client
import { apiFetch } from '@/infrastructure/apiFetch';
const res = await apiFetch('/api/tasks');
Exception: The centralized client itself may use raw fetch or axios internally — that is its implementation detail, not a bypass.
The audit's Integration Contracts dimension (Phase 1.5, Dimension A) checks compliance with this rule. Any direct
fetch/axioscall outside the shared client is a[INT]finding.
Prefer named exports over default exports
// ✅ Named — explicit, refactor-safe, IDE-friendly
export function createTask() { ... }
export type { Task };
// ❌ Default — ambiguous import names, harder to auto-import
export default function createTask() { ... }
Avoid barrel re-exports that create circular dependency risk
index.ts files only for the public API of a feature, not as catch-all re-exportsImport type separately to avoid bundling runtime artifacts
import type { Task } from './types';
Test naming, file conventions, and pyramid proportions are defined in
testing-strategy.md. This section covers TypeScript-specific tooling.
Type your mocks with Vitest types — never use as any in test doubles
import { vi } from 'vitest';
import type { MockedObject } from 'vitest';
const mockStore: MockedObject<TaskStore> = {
create: vi.fn(),
getById: vi.fn(),
};
Assert on error types, not just error messages
await expect(service.create(invalid)).rejects.toThrow(ZodError);
Use satisfies operator in tests for type-checked fixtures
const fixture = {
id: 'abc', title: 'Test task'
} satisfies Task;
| Tool | Purpose | Notes |
|---|---|---|
vue-tsc --noEmit | Full type checking (incl. .vue files) | Must pass zero errors; use tsc --noEmit for non-Vue TS projects |
eslint | Lint rules + style | Use @typescript-eslint/recommended-type-checked |
prettier | Canonical formatting | Non-negotiable |
npm audit / pnpm audit | Dependency CVE scanning | Run in CI; fail on high severity |
See code-idioms-and-conventions.md for the exact commands to run before committing.