본문으로 건너뛰기
cccskills
무료GitHub에 공개

typescript-standards

Shared TypeScript coding standards for strict, immutable, type-safe code.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

설치 방법 보기

포함된 파일(6)

  • SKILL.md15.1 KB
  • resources/advanced-types.md4.2 KB
  • resources/banned-operations.md2.8 KB
  • resources/error-handling.md4.4 KB
  • resources/immutability.md2.0 KB
  • resources/module-system.md6.6 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

TypeScript Standards Skill

Shared standards for all TypeScript code in this methodology (backend and frontend).


Strict TypeScript Configuration

All projects must use these TypeScript compiler options:

// tsconfig.json requirements
{
  "strict": true,
  "noImplicitAny": true,
  "strictNullChecks": true,
  "strictFunctionTypes": true,
  "strictPropertyInitialization": true,
  "noImplicitThis": true,
  "alwaysStrict": true
}

Rules:

  • All types explicitly declared
  • No any unless absolutely unavoidable (must be justified)
  • Prefer unknown over any

Immutability (Non-Negotiable)

Use readonly on all properties, ReadonlyArray<T> for arrays, Readonly<T> / ReadonlyMap / ReadonlySet for generic types. Use const exclusively (never let or var). Use spread operators for updates — never mutate.

See immutability.md for full examples and functional alternatives to let.


Banned Mutable Operations

CRITICAL: .push(), .pop(), .shift(), .unshift(), .splice(), .sort(), .reverse(), .fill() on arrays; obj.prop = x, delete obj.prop, Object.assign(target, ...) on objects; .set(), .delete(), .add(), .clear() on Maps/Sets — all strictly forbidden. Use spread operators and immutable patterns instead.

See banned-operations.md for the complete reference tables with alternatives.


Arrow Functions Only

// GOOD: Arrow functions
const createUser = async (deps: Dependencies, args: CreateUserArgs): Promise<CreateUserResult> => {
  // ...
};

const handleClick = () => {
  // ...
};

// BAD: function keyword
async function createUser(deps: Dependencies, args: CreateUserArgs): Promise<CreateUserResult> {
  // ...
}

function handleClick() {
  // ...
}

Rule: Use arrow functions exclusively. Never use the function keyword.


No Classes or Inheritance

CRITICAL: Never use classes or inheritance unless creating a subclass of Error.

// GOOD: Types and functions
type User = {
  readonly id: string;
  readonly email: string;
  readonly createdAt: Date;
};

const createUser = (args: CreateUserArgs): User => ({
  id: generateId(),
  email: args.email,
  createdAt: new Date(),
});

// GOOD: Error subclass (only valid use of class)
class ValidationError extends Error {
  constructor(
    message: string,
    readonly field: string,
    readonly code: string
  ) {
    super(message);
    this.name = 'ValidationError';
  }
}

class NotFoundError extends Error {
  constructor(resource: string, id: string) {
    super(`${resource} with id ${id} not found`);
    this.name = 'NotFoundError';
  }
}

// BAD: Classes for domain objects
class User {
  constructor(
    public id: string,
    public email: string
  ) {}

  updateEmail(email: string) {
    this.email = email;  // Mutation!
  }
}

// BAD: Inheritance hierarchies
class Animal { /* ... */ }
class Dog extends Animal { /* ... */ }

// BAD: Service classes
class UserService {
  constructor(private db: Database) {}

  async createUser(args: CreateUserArgs) { /* ... */ }
}

Why:

  • Classes encourage mutation (methods that modify this)
  • Inheritance creates tight coupling and fragile hierarchies
  • Functions with explicit dependencies are easier to test and reason about
  • Error subclasses are the exception because they integrate with JavaScript's error handling (instanceof, stack traces)

Native JavaScript Only

// GOOD: Native methods
const filtered = users.filter(u => u.active);
const updated = { ...user, email: newEmail };
const mapped = Object.fromEntries(
  Object.entries(obj).map(([k, v]) => [k, v * 2])
);

// BAD: External utility libraries
import { map } from 'lodash';      // Never
import { produce } from 'immer';   // Never
import * as R from 'ramda';        // Never

Rule: Use only native JavaScript/TypeScript features. No utility libraries like lodash, ramda, or immer.

Why: Reduces bundle size, eliminates dependencies, forces understanding of native methods, ensures code remains maintainable without external library knowledge.


Module System Rules

Named exports only (never default exports). ES modules only (never CommonJS). index.ts files contain only imports/exports (no logic). Always import through index.ts (never bypass to implementation files). Inside a module, never import from its own index.ts — use relative paths to siblings. No file extensions in imports. Use @/ path alias for deep imports (2+ directory levels). Use import type for type-only imports.

See module-system.md for full rules with examples.


Interface vs Type

Rule: interface for function-only contracts (callbacks, loggers, handlers). type for everything else. Data types should not contain functions.

// GOOD: interface for function-only contracts
interface Logger {
  readonly info: (message: string, data?: unknown) => void;
  readonly warn: (message: string, data?: unknown) => void;
  readonly error: (message: string, data?: unknown) => void;
}

// GOOD: type for data shapes
type User = {
  readonly id: string;
  readonly email: string;
  readonly createdAt: Date;
};

type ServerMode = 'api' | 'worker' | 'cron';

type HelmSettings = HelmServerSettings | HelmWebappSettings;

// BAD: interface for data
interface User {
  readonly id: string;
  readonly email: string;
}

// BAD: type for function contracts
type Logger = {
  readonly info: (message: string) => void;
};

// BAD: functions inside data types
type User = {
  readonly id: string;
  readonly getDisplayName: () => string;  // Data types should not have methods
};

Semantic Type Aliases

Use type aliases to give meaning to primitives. A function accepting Milliseconds is self-documenting; a function accepting number is not.

// GOOD: Semantic aliases
type Milliseconds = number;
type Pixels = number;
type DatabaseProvider = 'postgresql';
type ServerMode = 'api' | 'worker' | 'cron';
type SpecStatus = 'pending' | 'in_progress' | 'ready_for_review' | 'approved';

type AnimationConfig = {
  readonly duration: Milliseconds;
  readonly delay: Milliseconds;
  readonly width: Pixels;
};

// BAD: Raw primitives with no meaning
type AnimationConfig = {
  readonly duration: number;  // Seconds? Milliseconds? Frames?
  readonly delay: number;
  readonly width: number;     // Pixels? Rem? Percent?
};

Advanced Types

Type guards for discriminated union narrowing. as const for literal arrays with typeof X[number] to derive union types. Generics for type-safe operations. keyof and indexed access types for type-safe property access. Object.entries / Object.fromEntries for immutable object transformations.

See advanced-types.md for full examples.


All Functions Must Return Values

Every function should return a meaningful value. Void functions are extremely rare and should be avoided — they hide information from callers and make code harder to compose.

// GOOD: Return a result that callers can use
const saveSettings = async (path: string, settings: SddConfig): Promise<CommandResult> => {
  try {
    await writeJson(path, settings);
    return { success: true, output: `Settings saved to ${path}` };
  } catch (err) {
    const message = err instanceof Error ? err.message : String(err);
    return { success: false, error: `Failed to save settings: ${message}` };
  }
};

// GOOD: Even simple operations can return useful info
const addToCache = (cache: ReadonlyMap<string, string>, key: string, value: string): ReadonlyMap<string, string> =>
  new Map([...cache, [key, value]]);

// BAD: Void function hides outcome from caller
const saveSettings = async (path: string, settings: SddConfig): Promise<void> => {
  await writeJson(path, settings);  // Caller can't tell if it worked
};

// BAD: Side-effect-only function
const logMetrics = (data: Metrics): void => {
  console.log(JSON.stringify(data));
};

Rule: All functions should return values. If a function has nothing meaningful to return, that's a signal the design should be reconsidered.

Exception: Callback signatures in interface contracts (like Logger) may use void return types, since the caller doesn't consume the return value. This is the only acceptable use of void.


Error Handling

Result unions over null — discriminated union return types for failable operations (never return null or throw for expected failures). Error catch blocks narrow with instanceof Error. External data validated at system boundaries.

See error-handling.md for full patterns with examples.


Async/Promise Patterns

// GOOD: Explicit Promise<T> return type
const loadSettings = async (path: string): Promise<CommandResult> => {
  // ...
};

// GOOD: Promise.all for concurrent independent operations
const validationResults = await Promise.all(
  specs.map((spec) => validateSpecFile(spec.path))
);

// GOOD: Promise.all with typed results
const results = await Promise.all(
  entries.map(async (entry): Promise<ReadonlyArray<string>> => {
    if (entry.isDirectory()) {
      return walkDir(fullPath, filter);
    }
    return [];
  })
);

// BAD: Sequential when parallel is possible
const result1 = await validateSpec(spec1);
const result2 = await validateSpec(spec2);  // Waits unnecessarily

// BAD: Missing return type annotation
const loadSettings = async (path: string) => {  // Return type unclear
  // ...
};

Null vs Undefined

For function return types, prefer result unions (see Error Handling). This section covers the distinction between null and undefined in type fields and when interacting with external APIs.

// GOOD: undefined via optional fields ("not provided")
type HelmServerSettings = {
  readonly deploy_modes?: ReadonlyArray<ServerMode>;  // Optional = may be undefined
};

// GOOD: Handling undefined from native APIs
const first = items.find(i => i.active);  // Returns T | undefined natively
if (first === undefined) {
  return { success: false, error: 'No active items found' };
}

// GOOD: Handling null from external/DOM APIs
const element = document.getElementById('root');  // Returns HTMLElement | null
if (element === null) {
  return { success: false, error: 'Root element not found' };
}

// BAD: Mixing null and undefined in your own types
type Config = {
  readonly host: string | null;     // Sometimes null
  readonly port: string | undefined; // Sometimes undefined
  // Inconsistent — use optional fields (undefined) for "not provided"
};

// BAD: Returning null from your own functions
const findComponent = (name: string): Component | null => {
  return components.find(c => c.name === name) ?? null;
  // Use a result union instead (see Error Handling)
};

Rule: Use undefined (via ?:) for optional type fields. Handle null/undefined from external APIs by converting to result unions. Never return null from your own functions — use result unions instead.


Record<string, never> for Empty Types

// GOOD: Record<string, never> for placeholder types
type ConfigSettings = Record<string, never>;
type TestingSettings = Record<string, never>;
type CicdSettings = Record<string, never>;

// These can be used in unions without accepting arbitrary data:
type ComponentSettings = ServerSettings | DatabaseSettings | ConfigSettings;

// BAD: Using {} or object for empty types
type ConfigSettings = {};        // Accepts any non-nullish value
type ConfigSettings = object;    // Too broad

Nullish Coalescing (??) vs Logical OR (||)

// GOOD: ?? for defaults (preserves 0, '', false)
const port = config.port ?? 3000;      // Only falls through on null/undefined
const name = config.name ?? 'default'; // '' is a valid name, kept as-is
const verbose = config.verbose ?? false;

// GOOD: || only when 0/''/false should also trigger the default
const displayName = user.nickname || user.email;  // Empty string -> use email

// BAD: || when 0 or '' are valid values
const port = config.port || 3000;  // port=0 becomes 3000 — wrong!
const count = config.count || 10;  // count=0 becomes 10 — wrong!

Rule: Default to ??. Only use || when you intentionally want to fall through on all falsy values.


Resource Files

For detailed guidance, read these on-demand:


Summary Checklist

Before committing TypeScript code, verify:

  • tsconfig.json has all strict mode options enabled
  • All interface/type properties use readonly
  • All arrays use ReadonlyArray<T>
  • All objects/maps/sets use Readonly<T>, ReadonlyMap, ReadonlySet
  • All functions use arrow syntax (no function keyword)
  • No classes or inheritance (except Error subclasses)
  • No mutable array methods (.push(), .pop(), .shift(), .unshift(), .splice(), .sort(), .reverse())
  • No mutable object operations (obj.prop = x, obj['key'] = x, delete obj.prop)
  • No mutable Map/Set operations (.set(), .delete(), .add(), .clear())
  • Use spread operators and immutable patterns for all updates
  • No utility libraries (lodash, ramda, immer)
  • No default exports - only named exports (export const, export interface, etc.)
  • No CommonJS - only ES modules (import/export, never require/module.exports)
  • All index.ts files contain only imports/exports (no logic)
  • All imports go through index.ts - never import implementation files directly
  • No file extensions in imports - never .js, .ts, .tsx
  • Path aliases for deep imports - use @/ instead of ../../../
  • No any types without justification
  • All const declarations (never let, never var)
  • interface for function contracts only, type for everything else
  • Semantic type aliases for meaningful primitives
  • Type guards for discriminated union narrowing
  • as const for literal arrays; derive union types with typeof X[number]
  • All functions return values (no void)
  • Result unions over null — discriminated union return types for failable operations
  • Error catch blocks narrow with instanceof Error
  • External data validated at system boundaries
  • Async functions have explicit Promise<T> return types
  • import type for type-only imports
  • ?? for defaults (not ||)
  • No let — use const with .map/.reduce/ternaries

Input / Output

This skill defines no input parameters or structured output.

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Standards for authoring SDD plugin agents — frontmatter, self-containment, skill references, and no-user-interaction rules.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

LiorCohen/sdd432026년 2월 24일 업데이트

Scaffolds Node.js/TypeScript backend components with CMDO architecture, driven by component settings.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

LiorCohen/sdd432026년 2월 24일 업데이트

CMDO architecture standards for Node.js/TypeScript backends with strict layer separation.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

LiorCohen/sdd432026년 2월 24일 업데이트

Intent-to-command mappings for fullstack-typescript tech pack features.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

LiorCohen/sdd432026년 2월 24일 업데이트

Create change specification and implementation plan with dynamic phase generation. Supports feature, bugfix, refactor, and epic types.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

LiorCohen/sdd432026년 2월 24일 업데이트

Orchestrates the full change lifecycle — routes actions to phase-specific sub-files.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

LiorCohen/sdd432026년 2월 24일 업데이트

LiorCohen의 스킬 모두 보기

이 스킬의 문제 신고