TypeScript Expert
Strict TypeScript patterns for type safety, null handling, and modern best practices.
When to Use
Auto-activate on: .ts, .tsx, .js, .jsx files, type errors, ESLint issues, "fix types", "add types", "TypeScript help"
Explicit: the typescript skill [concern]
Do NOT invoke for: Vue-specific patterns (use mk:vue), visual design (use mk:frontend-design)
Workflow Integration
Operates in Phase 3 (Build GREEN). Output supports the developer agent.
Process
- Detect concern — type error? new code? refactor? ESLint config?
- Load relevant reference — strict-null, type-safety, utility-types, or eslint
- Apply patterns — implement using strict TS patterns from references
- Verify —
npx tsc --noEmit must pass with zero errors
Core Rules (always apply)
- NEVER use
any — use unknown + type guards (security-rules.md)
- NEVER use
type assertion — use unknown + type guards (security-rules.md)
- NEVER use implicit truthiness for null checks — use explicit
=== null || === undefined
- ALWAYS use strict TypeScript config:
strict: true, noUncheckedIndexedAccess: true
- ALWAYS use
type imports: import type { X } from 'y'
- PREFER named exports over default exports
- PREFER discriminated unions over type assertions
Anti-Patterns
| Don't |
Do Instead |
as any or as unknown as X |
Proper type narrowing with guards |
if (value) for null check |
if (value !== null && value !== undefined) |
export default |
export const X / export function X |
Object type |
Record<string, unknown> |
| Implicit return types |
Explicit return type annotations |
enum (runtime overhead) |
as const satisfies or union types |
Output Format
## TypeScript: {concern}
**Files:** {list of .ts/.tsx files modified}
**Config:** {tsconfig changes if any}
### Changes Applied
{numbered list of type improvements}
### Verification
{tsc --noEmit output — must show 0 errors}
References
| Reference |
When to load |
Content |
| strict-patterns.md |
Type safety work |
Null handling, discriminated unions, type guards, utility types |
| review-checklist.md |
When reviewing TypeScript code |
Prioritized checklist: CRITICAL (security), HIGH (type safety, async, errors), MEDIUM (React, perf) |
Failure Handling
| Failure |
Recovery |
tsc --noEmit fails after changes |
Fix errors before proceeding — never ship with type errors |
| No tsconfig.json found |
Suggest creating one with strict config |
| Third-party types missing |
npm install @types/{package} |
Gotchas
as cast silences narrowing errors and hides real bugs — value as User tells TS to trust you, not validate; a malformed API response passes the cast and crashes at runtime; use a type guard or Zod parse at the boundary instead.
moduleResolution: "bundler" breaks import type in non-bundler contexts — tsconfig moduleResolution: bundler (Vite/esbuild default) allows extensionless imports that Node.js --esm rejects at runtime; flip to nodenext for backend code or keep dual configs per target.
noUncheckedIndexedAccess: true makes every array index T | undefined — enabling this flag (required by strict config) means arr[0] is string | undefined not string; code that was type-correct before will error until every indexed access is null-guarded.
- Declaration merging in ambient
.d.ts files silently adds properties to global types — a declare module 'express' block in any .d.ts in the project augments Express's types globally; two libraries doing this with conflicting shapes cause TS2300 duplicate identifier errors that appear far from the source.
- Template literal types create unique brands that break assignability —
type UserId = \user_${string}`is not assignable fromstring; passing a plain stringwhereUserId` is expected fails even though values look identical at runtime; always brand at the input boundary with a parse function.
enum generates runtime JS objects, causing tree-shaking failures — enums are not erased; a const enum inside a library published as .js is inlined at compile time but not consumable by projects that don't re-compile the source; use as const satisfies patterns for exported enums.
1---2name: mk-typescript-23description: Use when writing TypeScript code, fixing type errors, or configuring strict type safety. Auto-activates on .ts/.tsx files. Covers null handling, utility types, and ESLint.4---56<!-- Improvements over source skills:7- Split from 361-line monolith to SKILL.md + references/ (Phase 1: CLASS A)8- Framework-agnostic (removed React-specific patterns from frontend-development)9- Integrated with security-rules.md (no `any` types already enforced)10- Added workflow phase anchoring (Phase 3 Build)11-->1213# TypeScript Expert1415Strict TypeScript patterns for type safety, null handling, and modern best practices.1617## When to Use1819**Auto-activate on:** `.ts`, `.tsx`, `.js`, `.jsx` files, type errors, ESLint issues, "fix types", "add types", "TypeScript help"2021**Explicit:** `the typescript skill [concern]`2223**Do NOT invoke for:** Vue-specific patterns (use mk:vue), visual design (use mk:frontend-design)2425## Workflow Integration2627Operates in **Phase 3 (Build GREEN)**. Output supports the `developer` agent.2829## Process30311. **Detect concern** — type error? new code? refactor? ESLint config?322. **Load relevant reference** — strict-null, type-safety, utility-types, or eslint333. **Apply patterns** — implement using strict TS patterns from references344. **Verify** — `npx tsc --noEmit` must pass with zero errors3536## Core Rules (always apply)3738- **NEVER** use `any` — use `unknown` + type guards (security-rules.md)39- **NEVER** use `type assertion` — use `unknown` + type guards (security-rules.md)40- **NEVER** use implicit truthiness for null checks — use explicit `=== null || === undefined`41- **ALWAYS** use strict TypeScript config: `strict: true`, `noUncheckedIndexedAccess: true`42- **ALWAYS** use `type` imports: `import type { X } from 'y'`43- **PREFER** named exports over default exports44- **PREFER** discriminated unions over type assertions4546## Anti-Patterns4748| Don't | Do Instead |49| ----------------------------- | -------------------------------------------- |50| `as any` or `as unknown as X` | Proper type narrowing with guards |51| `if (value)` for null check | `if (value !== null && value !== undefined)` |52| `export default` | `export const X` / `export function X` |53| `Object` type | `Record<string, unknown>` |54| Implicit return types | Explicit return type annotations |55| `enum` (runtime overhead) | `as const` satisfies or union types |5657## Output Format5859```60## TypeScript: {concern}6162**Files:** {list of .ts/.tsx files modified}63**Config:** {tsconfig changes if any}6465### Changes Applied66{numbered list of type improvements}6768### Verification69{tsc --noEmit output — must show 0 errors}70```7172## References7374| Reference | When to load | Content |75| --------------------------------------------------------- | ---------------- | --------------------------------------------------------------- |76| **[strict-patterns.md](./references/strict-patterns.md)** | Type safety work | Null handling, discriminated unions, type guards, utility types |77| **[review-checklist.md](./references/review-checklist.md)** | When reviewing TypeScript code | Prioritized checklist: CRITICAL (security), HIGH (type safety, async, errors), MEDIUM (React, perf) |7879## Failure Handling8081| Failure | Recovery |82| ---------------------------------- | ---------------------------------------------------------- |83| `tsc --noEmit` fails after changes | Fix errors before proceeding — never ship with type errors |84| No tsconfig.json found | Suggest creating one with strict config |85| Third-party types missing | `npm install @types/{package}` |8687## Gotchas8889- **`as` cast silences narrowing errors and hides real bugs** — `value as User` tells TS to trust you, not validate; a malformed API response passes the cast and crashes at runtime; use a type guard or Zod parse at the boundary instead.90- **`moduleResolution: "bundler"` breaks `import type` in non-bundler contexts** — tsconfig `moduleResolution: bundler` (Vite/esbuild default) allows extensionless imports that Node.js `--esm` rejects at runtime; flip to `nodenext` for backend code or keep dual configs per target.91- **`noUncheckedIndexedAccess: true` makes every array index `T | undefined`** — enabling this flag (required by strict config) means `arr[0]` is `string | undefined` not `string`; code that was type-correct before will error until every indexed access is null-guarded.92- **Declaration merging in ambient `.d.ts` files silently adds properties to global types** — a `declare module 'express'` block in any `.d.ts` in the project augments Express's types globally; two libraries doing this with conflicting shapes cause TS2300 duplicate identifier errors that appear far from the source.93- **Template literal types create unique brands that break assignability** — `type UserId = \`user_\${string}\`` is not assignable from `string`; passing a plain `string` where `UserId` is expected fails even though values look identical at runtime; always brand at the input boundary with a parse function.94- **`enum` generates runtime JS objects, causing tree-shaking failures** — enums are not erased; a `const enum` inside a library published as `.js` is inlined at compile time but not consumable by projects that don't re-compile the source; use `as const` satisfies patterns for exported enums.