Memory

TypeScript

Try it

Decode TypeScript type errors and design types for APIs, tsconfig, and .d.ts files.

What it does

Reads TypeScript diagnostics bottom-up and applies strict-typing rules: `unknown` at trust boundaries, discriminated unions in place of multi-optional fields, `satisfies` for config objects, annotated exports with inferred bodies, exhaustiveness via `never`. Covers type-error decoding (not assignable, TS2589, TS2742), generic inference and overload design, tsconfig flags beyond `strict`, .d.ts authoring, ESM/CJS interop repair, and tsc/editor performance triage. Detailed playbooks for each topic live in companion docs; user preferences load from a local config file.

When to use it

  • Stuck on a 40-line "not assignable" error in CI that only appears there
  • Designing a discriminated union to replace `{ data?, error? }` optional soup
  • Writing or fixing .d.ts for an untyped npm package, including publishing types
  • Enabling beyond-`strict` flags like noUncheckedIndexedAccess or exactOptionalPropertyTypes

The skill document

User preferences and memory live in ~/Clawic/data/typescript/ (see setup.md on first use, memory-template.md for the file format). If you have data at an old location (~/typescript/ or ~/clawic/typescript/), move it to ~/Clawic/data/typescript/.

When To Use

  • Decoding type errors: "not assignable" walls, narrowing failures, inference surprises, errors that appear only in CI
  • Designing types for an API surface: generics, discriminated unions, utility types, branded types
  • tsconfig decisions: strict flags, module resolution, target/lib, monorepo project references, JS-to-TS migration order
  • Writing or fixing declaration files (.d.ts, module augmentation, publishing types)
  • Code that compiles green but fails at runtime — ESM/CJS interop crashes, unvalidated external data — or tsc/editor slowness
  • Not for runtime JavaScript semantics (closures, event loop, coercion) — that is the javascript skill

Quick Reference

SituationPlay
Value of unknown shape (API, JSON.parse, catch)unknown + narrow before use; never any
Config/lookup object losing literal typessatisfies (TS >=4.9), not a type annotation
Union variant not narrowingAdd a literal discriminant field + exhaustive never check
40-line "not assignable" errorRead the deepest Types of property ... are incompatible line first — root cause is at the bottom; full decoder → errors.md
Cryptic diagnostic (TS2589, TS2742, "no overload matches"), error only in CIerrors.md
Generic won't infer / variance confusion / overload designgenerics.md
Partial/Omit/Record behaving oddlyutility-types.md
Untyped npm package, globals, augmentation, publishing typesdeclarations.md
JS conversion, strict-flag rollout, as debt, TS version upgrademigration.md
target/module/paths choices, monorepo, project referencestsconfig.md
Green compile, runtime crash (x is not a function, ERR_REQUIRE_ESM)modules.md
External data crossing a trust boundaryValidate at runtime at the boundary; static types only downstream → boundaries.md
tsc slow, editor lag, compiler out of memoryperformance.md
Anything else typedDefault: full strict, infer locals, annotate exports

Depth on demand: errors.md diagnostic decoding · generics.md inference, variance, conditional types, overloads · utility-types.md built-in type traps · declarations.md .d.ts, augmentation, publishing · migration.md JS-to-TS, strictness ratchets, TS upgrades · tsconfig.md compiler configuration · modules.md ESM/CJS interop · boundaries.md runtime validation · performance.md compile and editor speed.

Core Rules

  1. Full strict or you are reviewing half the code. Every flag off is a bug class the checker won't report. Greenfield: strict: true day one plus noUncheckedIndexedAccess (TS >=4.1). Legacy: stage flags with a ratchet (migration.md).
  2. unknown in, typed out. Data you didn't construct (network, JSON, env, storage) enters as unknown and gets validated once at the boundary (boundaries.md); past that point no any and no re-checking.
  3. Annotate exports, infer locals. Annotated parameters and return on every exported function put errors at the boundary that caused them, not at 30 call sites downstream; inference inside bodies keeps the noise down.
  4. Make illegal states unrepresentable. n independent optional fields admit 2^n combinations: { data?: T; error?: E } is 4 states of which 2 are illegal. A discriminated union with one variant per legal state encodes exactly the truth.
  5. Every as carries a proof. A cast overrides the checker — attach the runtime guard that makes it true, or a one-line comment saying why it cannot be wrong. An unexplained cast is a deferred bug report.
  6. Exhaustive by construction. Every switch over a union ends in default: x satisfies never (TS >=4.9) — adding a variant then becomes a compile error at every site that must handle it, which is the entire point of the union.
  7. Errors read bottom-up. The first lines of a long diagnostic are wrappers; the root cause is the deepest Types of property ... are incompatible line (errors.md).

Stop Using any

  • any is viral both directions: it silences errors on reads AND poisons everything it's assigned to. unknown only blocks reads until you narrow — same flexibility, none of the spread
  • catch (e) is unknown under strict since TS 4.4 (useUnknownInCatchVariables) — check e instanceof Error before .message
  • Every remaining cast (as) gets either a runtime guard above it or a one-line comment saying why it's safe (rule 5)
  • Ratchet, don't boil the ocean: count anys (typescript-eslint no-explicit-any + no-unsafe-*), record the baseline, fail CI when the count rises

Inference & Widening

Widening:

  • let x = "hello" is string; const x = "hello" is "hello" — mutability drives widening
  • Object and array literals widen their members even under const: const cfg = { mode: "dark" } has mode: stringas const freezes the whole tree (readonly + literals)
  • Literal arguments: primitives infer as literals, objects/arrays widen — `` type parameters (TS >=5.0) preserve them at the call site
  • Function return types widen too — annotate the return when a caller needs the literal

Inference limits:

  • Inference flows from arguments to parameters, not through intermediate generics — when a nested generic fails, name the intermediate type and split into two steps
  • No partial type argument inference: fn with one explicit argument turns off inference for the rest — use the curried two-call pattern make()(config) to fix one and infer the other
  • NoInfer (TS >=5.4) excludes a position from inference: declare function set(vals: T[], initial: NoInfer): void — without it, a wrong initial widens T instead of erroring
  • Callback parameters get contextual types only when the expected signature is known — assigning the function to a typed variable or parameter is what enables (x) => without annotation

Discriminated Unions & Narrowing

Modeling with unions:

  • Add a literal kind field to each variant — narrowing and exhaustive switches come free (rules 4 and 6)
  • Discriminant must be a literal type — a field typed string discriminates nothing; as const the source values

Narrowing failures:

  • filter(Boolean) doesn't narrow in any TS version — write .filter((x): x is T => Boolean(x)). TS >=5.5 infers predicates from simple lambdas, so .filter(x => x != null) narrows there; below 5.5 it doesn't
  • Narrowing dies inside callbacks: after if (x), arr.map(() => x.foo) re-errors because TS assumes x may be reassigned — copy to a const before the callback
  • typeof x === "object" includes null — check x !== null in the same condition
  • Object.keys(obj) returns string[], not keyof typeof obj — intentional: structural types can carry extra keys. Cast only when you own the object literal
  • Array.isArray() on unknown narrows to any[] — element type needs its own check
  • in narrows unions only when the property appears in exactly one branch; on unlisted properties it narrows since TS 4.9
  • Destructured discriminants (const { kind } = action; if (kind === ...)) narrow only in TS >=4.6 — on older versions switch on action.kind directly

satisfies vs Annotation vs Cast

Three tools, three guarantees — pick by what you need to keep:

  • const x: T = v — checks AND widens to T: literal info gone, excess properties rejected
  • const x = v satisfies T (TS >=4.9) — checks compatibility, keeps the inferred narrow type: the default for config/route/theme objects
  • v as T — checks nothing beyond rough overlap; "hello" as number fails but {} as User compiles. It's an assertion, not a conversion

Strict Null Handling

  • ?? vs ||: port || 3000 turns a legitimate 0 into 3000; port ?? 3000 only replaces null/undefined. Same for "" in string options
  • Optional chaining ?. produces undefined, never null — APIs contracted to null need an explicit ?? null
  • Non-null ! is a cast in disguise — prefer early return or narrowing; each ! is a runtime crash candidate the compiler can no longer see

Strictness Beyond strict

strict: true is not the strictest configuration. Flags it does NOT include:

  • noUncheckedIndexedAccess (TS >=4.1) — arr[i] and record[key] become T | undefined. Catches the most common real crash (indexing off the end); enable on greenfield day one, on legacy expect a large error wave and stage it
  • exactOptionalPropertyTypes (TS >=4.4) — distinguishes "absent" from "explicitly undefined"; breaks code that assigns undefined to optional props
  • noPropertyAccessFromIndexSignature, noImplicitOverride — cheap to adopt, low churn
  • verbatimModuleSyntax (TS >=5.0) — forces import type for type-only imports; the modern replacement for isolatedModules import hygiene (modules.md)

Everything else about compiler configuration — target/lib, module × moduleResolution, paths, monorepos — lives in tsconfig.md; import/export mechanics and transpile-only compatibility in modules.md.

Where To Annotate

  • Default: annotate exported function signatures (parameters AND return), infer everything inside bodies (rule 3)
  • Explicit return types on the public surface also unlock isolatedDeclarations (TS >=5.5) and faster declaration emit
  • A type parameter must relate at least two positions (two params, or param and return). Used once, it's decoration — replace with unknown or the concrete type
  • Slow type checking has structural causes and a measurement workflow — performance.md

Output Gates

Before emitting TypeScript, verify:

  • No any introduced, explicit or implicit — unknown where the shape is genuinely open?
  • Exported functions annotated, parameters and return?
  • Every as has a runtime guard above it or a justifying comment?
  • Every switch over a union ends in an exhaustiveness check?
  • External data validated at runtime before its first typed use?
  • No ! where narrowing or ?? would prove it?

Configuration

User-dependent variables. Defaults apply until the user states a preference; store them in ~/Clawic/data/typescript/config.yaml.

VariableTypeDefaultEffect
runtime_targetnode | browser | bun | deno | isomorphicnodeDrives module/moduleResolution and lib advice in tsconfig.md and import-extension rules in modules.md
project_kindapp | libraryappLibrary flips guidance to declaration emit, exports-map types, and wider TS-version support; app defaults to skipLibCheck and bundler resolution
compile_pipelinetsc | transpile-onlytranspile-onlytranspile-only (esbuild, swc, Babel, bundlers) enforces the single-file-safe subset: no const enum, isolatedModules rules apply (modules.md); tsc lifts those bans
validation_libraryzod | valibot | arktype | ajv | nonenoneNames the schema library used in boundary-validation examples (boundaries.md); none keeps guidance library-neutral
ts_versiontext (e.g. "5.4")latestGates every TS >=X.Y-marked recommendation in this skill: below the mark, the pre-version workaround is offered instead (e.g. below 4.9 an annotation replaces satisfies)

Preference areas to record as the user reveals them:

  • conventionsinterface vs type default, type naming style, banned features (enums, namespaces, decorators), type-only import style
  • strictness posture — which beyond-strict flags to push proactively, cast tolerance, error-suppression policy
  • tooling — package manager, typescript-eslint strictness, monorepo layout (project references vs single config), type-test tooling
  • output format — quick fix vs explained fix, whether to show before/after diffs

Traps

TrapWhy it failsDo instead
enumThe one TS feature with runtime emit; const enum breaks under transpile-only compilersas const object + keyof typeof (modules.md)
Function or {} as a typeFunction accepts any callable and calls return any; {} accepts everything except null/undefinedExact signature (a: A) => R; Record for bags
Trusting excess property checks as validationThey fire only on fresh object literals — a value passed through a variable skips themRuntime validation at boundaries (boundaries.md)
// @ts-ignoreKeeps suppressing after the error is fixed, hiding the next one// @ts-expect-error (TS >=3.9) — errors itself once stale
! to silence strictNullChecksEach one is an invisible cast and a crash candidateNarrow, early-return, or ??; sanctioned only for framework-assigned class fields (migration.md)
[key: string]: any to quiet index errorsPoisons every access on the object with anyPrecise keys, Record, or a Map; noUncheckedIndexedAccess (TS >=4.1) keeps even honest signatures honest

Where Experts Disagree

  • interface vs type. Default: interface for object shapes (merges for augmentation, caches relationships for faster checking), type for unions, functions, tuples, and mapped/conditional results. An existing codebase convention beats either.
  • Explicit return types everywhere vs boundary-only. Boundary-only is the default (rule 3). The everywhere school wins on published libraries and under isolatedDeclarations (TS >=5.5), where emit requires them anyway.
  • Runtime validation depth. Edges-only is the default — validating internally recomputes what the checker already proved. The schema-first school (schema as source of truth, static types inferred from it) wins when one shape crosses many boundaries (boundaries.md).

More Clawic skills, get them at https://clawic.com/skills/typescript (install if the user confirms):

  • javascript — runtime semantics: coercion, closures, event loop
  • react — typing components, hooks, and props in practice
  • nodejs — Node runtime, packaging, and ESM/CJS beyond the type layer
  • deno — TypeScript-native runtime without a build step

Feedback

Part of Clawic, the verified skill library. Get this skill: https://clawic.com/skills/typescript.

Questions people ask

Does it handle runtime JavaScript bugs like closures, the event loop, or coercion?
No. The skill is explicit that runtime JavaScript semantics are out of scope and belong to a different JavaScript-focused skill. This one sticks to the type system, compiler configuration, and type-shaped runtime failures (ESM/CJS interop, unvalidated boundaries).
Which TypeScript version does it assume?
Latest by default. Recommendations tagged with a `TS >= X.Y` threshold fall back to the pre-version workaround when the configured `ts_version` is older, for example swapping `satisfies` (4.9) back to a type annotation or `const x = v as T` if you are below that.
Can I keep using `any`?
Not encouraged. The skill pushes `unknown` plus narrowing at trust boundaries and treats `any` as viral (silences reads, poisons assignments). It expects you to count `any` via typescript-eslint and fail CI when the count rises.

Related skills

Translate a TypeScript compiler error into plain English, find the smallest type boundary at fault, and prefer the smallest safe fix over suppression.

4 installs

Debug and write JavaScript across Node and browsers, grounded in runtime semantics, async behavior, and feature-floor checks.

by Iván136 installs6 stars

Diagnose and fix Node.js runtime, packaging, and production issues with concrete rules and reference files.

143 installs5 stars

Use this skill whenever a user wants to run TypeScript, TSX, or React in the browser without a build step.

1 installs

Detect your Effect v3 or v4 install and generate API-correct Effect TypeScript across error, concurrency, schema, HTTP, SQL, and AI layers.

27 installs1 stars

TypeScript编码规范工具免费版为个人开发者提供符合行业惯例的TypeScript编码约定,涵盖命名规范、类型与接口、函数与类、模块与导入等基础主题。核心能力: - 命名规范指导(变量、函数、类、接口、枚举) - 类型与接口选择策略 - 函数与类的最佳实践 - 模块与导入顺序约定 - 错误处理与空值处理基础 适用场景: - 个人TypeScript项目编码规范落地 - 学习并应用业界主流TS编码约定 - 快速审查代码风格问题 差异化:免费版聚焦个人开发者的基础规范需求,提供核心约定与示例