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
| Principle | Rule |
|---|---|
| No 'any' | Use unknown and narrow, or define precise types |
| Explicit return types | Required on all public APIs |
| readonly by default | Use readonly unless mutation is required |
| async/await only | No callbacks or .then() chains |
| Typed errors | Custom error classes or discriminated-union results |
| Composition over inheritance | Prefer interfaces and composition |
| Derive types from data | keyof typeof, Pick/NonNullable off a library type — never retype a shape by hand |
| Validate at boundaries | Narrow unknown with a type guard or schema, never cast unverified input |
| Illegal states unrepresentable | Discriminated unions over boolean-soup flags; scoped branded types for real ID/unit mix-ups |
Reference
Anti-patterns
| Anti-pattern | Preferred |
|---|---|
type Foo = any | type Foo = unknown (then narrow) |
function f(): any | function f(): ConcreteType |
.then(cb).catch(cb) | async/await with try/catch |
class B extends A | interface I; class B implements I |
try { } catch(e) { e.message } | catch(e) { if (e instanceof MyError) ... } |
as SomeType | satisfies or explicit narrowing |
const x: T = {...} | const x = {...} as const satisfies T |
JSON.parse(x) as T | narrow with a type guard or schema (zod, envalid) |
standalone types.ts | colocate the type with its consumer |
| boolean-soup state | discriminated union |
switch / if-else chain on a closed set | Record dispatch table, or a config array reduced/found over |
| nested ternary | Record dispatch table, or a config array found over |
if (url.startsWith("https:")) | extract to a named const: HTTPS_SCHEME |
options.retries ??= 3 | return { retries: 3, ...options } (no param mutation) |
catch { /* ignored */ } | rethrow a typed error, never swallow silently |
| class with no private state | a plain function |
// @ts-ignore alone | // @ts-ignore — TODO(TICKET): ... |
items.length === 0 / count > 0 | !items.length / count (coerce any zero-check, not just .length) |