Skip to content

Claude Code plugin

TypeScript Patterns Enforcer

TypeScript best practices — inline guidance and a dedicated reviewer.

Three components: an inline skill that watches .ts/.tsx files and guides in real time, a subagent for full PR reviews, large refactors, and JavaScript-to-TypeScript migrations, and a hook that runs ten mechanical checks after every .ts or .tsx edit.

Getting started

Install

# via gago-plugins marketplace
/plugin marketplace add gagoar/gago-plugins
/plugin install typescript-patterns-enforcer@gago-plugins
/reload-plugins
# standalone
/plugin marketplace add gagoar/typescript-patterns-enforcer
/plugin install typescript-patterns-enforcer@typescript-patterns-enforcer
/reload-plugins

Renamed from ts-patterns

Before v0.5.0 this plugin was called ts-patterns. The plugin directory held that name as too close to other plugins. If you installed the old name, run /plugin uninstall ts-patterns@gago-plugins, then install the new one.

The skill is now /typescript-patterns-enforcer:check and the agent is typescript-patterns-enforcer:review. Three things keep the old name, so nothing you wrote needs to change: the ts-patterns: prefix on the hook's messages, the ts-patterns/<rule> names in eslint-disable comments, and the ts-patterns: allow exhaustive switch line in a project's CLAUDE.md.

What's included

Components

  • /typescript-patterns-enforcer:checkInline skill. Automatically active while writing or editing TypeScript. Provides inline guidance on types, patterns, and anti-patterns without being invoked.
  • typescript-patterns-enforcer:reviewSubagent. Spawn via the Agent tool for PR reviews, large refactors, or converting JavaScript to TypeScript. Runs as a dedicated subagent.
  • HookRuns a bundled checker after every .ts or .tsx edit and backs the skill with ten deterministic checks.

Reference

Core principles

PrincipleRule
No 'any'Use unknown and narrow, or define precise types
Explicit return typesRequired on all public APIs
readonly by defaultUse readonly unless mutation is required
async/await onlyNo callbacks or .then() chains
Typed errorsCustom error classes or discriminated-union results
Composition over inheritancePrefer interfaces and composition
Derive types from datakeyof typeof, Pick/NonNullable off a library type — never retype a shape by hand
Validate at boundariesNarrow unknown with a type guard or schema, never cast unverified input
Illegal states unrepresentableDiscriminated unions over boolean-soup flags; scoped branded types for real ID/unit mix-ups

Reference

Anti-patterns

Anti-patternPreferred
type Foo = anytype Foo = unknown (then narrow)
function f(): anyfunction f(): ConcreteType
.then(cb).catch(cb)async/await with try/catch
class B extends Ainterface I; class B implements I
try { } catch(e) { e.message }catch(e) { if (e instanceof MyError) ... }
as SomeTypesatisfies or explicit narrowing
const x: T = {...}const x = {...} as const satisfies T
JSON.parse(x) as Tnarrow with a type guard or schema (zod, envalid)
standalone types.tscolocate the type with its consumer
boolean-soup statediscriminated union
switch / if-else chain on a closed setRecord dispatch table, or a config array reduced/found over
nested ternaryRecord dispatch table, or a config array found over
if (url.startsWith("https:"))extract to a named const: HTTPS_SCHEME
options.retries ??= 3return { retries: 3, ...options } (no param mutation)
catch { /* ignored */ }rethrow a typed error, never swallow silently
class with no private statea plain function
// @ts-ignore alone// @ts-ignore — TODO(TICKET): ...
items.length === 0 / count > 0!items.length / count (coerce any zero-check, not just .length)