Claude Code plugin
Claude writes TypeScript. TypeScript Patterns Enforcer checks every edit.
An inline skill, a review subagent, and a hook that runs ten checks after every .ts edit and sends violations back to Claude.
/plugin marketplace add gagoar/typescript-patterns-enforcer /plugin install typescript-patterns-enforcer@typescript-patterns-enforcer /reload-plugins
See it work
Claude writes it. The hook flags it. Claude fixes it.
Toggle the same function before and after the hook runs. Each file below is one of the failing and passing fixtures from engine/test/fixtures/.
export function describeStatus(status: string): string { return status === "active" ? "Active" : status === "paused" ? "Paused" : "Unknown";}
const STATUS_LABELS: Record<string, string> = { active: "Active", paused: "Paused" };export function describeStatus(status: string): string { return STATUS_LABELS[status] ?? "Unknown";}
ts-patterns: 1 mechanical violation(s) in describeStatus.ts:
L2:10 no-nested-ternary nested ternary — flatten, or convert to a Record/.find() lookup (see SKILL.md Data Over Logic). → SKILL.md Data Over Logic — flatten or convert nested ternaries to a Record/.find() lookup
Fix these to satisfy the TypeScript patterns before continuing.
exit code 2 (stderr goes back to Claude)
clean no output
exit code 0
export function describeRole(role: string): string { switch (role) { case "admin": return "Admin"; default: return "Guest"; }}
const ROLE_LABELS: Record<string, string> = { admin: "Admin" };export function describeRole(role: string): string { return ROLE_LABELS[role] ?? "Guest";}
ts-patterns: 1 mechanical violation(s) in describeRole.ts:
L2:3 no-switch-statement switch statement — convert to a Record dispatch table (see SKILL.md Data Over Logic). → SKILL.md Data Over Logic — convert to a Record dispatch table
Fix these to satisfy the TypeScript patterns before continuing.
exit code 2 (stderr goes back to Claude)
clean no output
exit code 0
export function identify(value: any): void { console.log(value);}
export function identify(value: unknown): void { console.log(value);}
ts-patterns: 1 mechanical violation(s) in identify.ts:
L1:33 no-explicit-any Unexpected any. Specify a different type. → SKILL.md Core Rule 1 — use unknown + narrow, or a precise type
Fix these to satisfy the TypeScript patterns before continuing.
exit code 2 (stderr goes back to Claude)
clean no output
exit code 0
export function loadUser(id: string): Promise<string> { return fetch(id).then((response) => response.text());}
export async function loadUser(id: string): Promise<string> { const response = await fetch(id); return response.text();}
ts-patterns: 1 mechanical violation(s) in loadUser.ts:
L2:20 prefer-await-to-then Prefer await to then()/catch()/finally(). → SKILL.md Core Rule 5 — use async/await, not .then()
Fix these to satisfy the TypeScript patterns before continuing.
exit code 2 (stderr goes back to Claude)
clean no output
exit code 0
Automatic
Runs after every edit.
The hook reads the file Claude just edited. If a check fails, the message goes to Claude and Claude fixes the file before moving on. Clean files produce no output.
- TriggerOne
PostToolUsehook, inhooks/hooks.json. It runsnode engine/dist/check.js. - ScopeOnly
.tsand.tsxfiles. - OutputViolations go to stderr with exit code 2, which sends them back to Claude.
- Side effectsNone: the engine's own code writes nothing to disk, makes no network calls, and starts no other process.
- No NodeIf Node is missing or the check cannot run, the hook exits with no output. The skill's written rules then remain the only enforcement.
What you get
Three parts, one standard.
/typescript-patterns-enforcer:check is active while Claude writes or edits TypeScript. It gives inline guidance on type safety, patterns, and anti-patterns.
typescript-patterns-enforcer:review handles full PR reviews, large refactors, and JavaScript to TypeScript conversions.
A bundled checker runs after every .ts or .tsx edit. It brings its own parser, and it ignores your repo's config.
The core rules
Eleven rules for the skill to enforce.
The skill also covers data over logic, validation at the boundary, and comment discipline. A hook tag marks a rule the hook also checks mechanically.
- No anyhookUse unknown and narrow, or write a precise type.
- Explicit return typesAnnotate public functions.
- interface vs typeinterface for extendable shapes. type for unions, intersections, and mapped types.
- readonly by defaultMutable only when mutation is required.
- async/await onlyhookNo
.then()chains. ReturnPromise<T>explicitly. - Typed errorsCustom error classes or discriminated-union results.
- Constrained generics
<TEntity extends BaseEntity>, not<T>. - Composition over inheritancePlain functions and utility types first.
- No magic valueshook: numbersName repeated or meaningful literals in comparisons or branching.
- Coerce over compare
if (!items.length), notitems.length === 0. - Every regex carries an examplehookA comment directly above it: plain words, a quoted match and reject, and a pre-filled regex101 link.
The ten mechanical checks
The checks find the pattern. Rewriting it well stays with Claude and the skill, because a linter can ban a switch but cannot rewrite one.
| Check | Catches |
|---|---|
no-explicit-any | A bare any |
ban-ts-comment | @ts-ignore with no description |
no-magic-numbers | Unnamed numeric literals |
prefer-await-to-then | .then() chains |
no-switch-statement | switch statements |
no-param-reassign | Parameter mutation |
no-loop-statements | Raw for and while loops |
no-nested-ternary | Nested ternaries |
require-regex-example | A regex literal with no plain-words comment, example, or tester link above it. Advisory: it checks for a quoted string and an https:// link. |
no-narrative-comment | Comments that narrate history. Advisory: it matches a list of phrases, so it misses some and flags some. |
Evidence
Tested before it ships.
Each has a failing fixture and a passing fixture. The test suite runs against the built hook.
The engine bundles its own parser and rules. It never reads or writes your repo's setup.
Open source. Fixtures live in engine/test/fixtures/.
Install it in 10 seconds.
/plugin marketplace add gagoar/typescript-patterns-enforcer /plugin install typescript-patterns-enforcer@typescript-patterns-enforcer /reload-plugins
Formerly ts-patterns. Installed that one? Run /plugin uninstall ts-patterns@gago-plugins, then install the new name. Details