Typescript
Git-versioned agent memory: agents that never make the same mistake twice. Anthropic-Skill folder standard, multi-runtime (Claude Code, Cursor, Gemini CLI, OpenCode).
npx -y skills add sordi-ai/skill-everything --skill typescriptAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 17 stars17 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Apply when writing TypeScript code. Strict types, discriminated unions, async patterns, and runtime safety.
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
8.5 KB, as published. Nobody here has run it
Sub-Skill: TypeScript Best Practices
<!-- target: ~2500 tokens (real tiktoken count) | 17 rules with severity classification -->Purpose: Prevents the TypeScript-specific mistakes LLMs make repeatedly — weak types, unsafe assertions, and patterns that compile but fail at runtime.
Rule classification
- MUST — load-bearing. Violating leaks runtime errors past the type checker. Never break.
- SHOULD — default behavior. Deviation needs a documented reason in the code or PR.
- AVOID — usually wrong; documented exception inline where needed.
Where these rules don't strictly apply: test fixtures, generated types (e.g. from GraphQL codegen, OpenAPI generators, Prisma), declaration files (*.d.ts) for untyped third-party libraries, and migration scripts may legitimately differ. The rules below apply to production application code.
Type Safety
-
MUST: Never use
any. Useunknownand narrow it.anydisables the type checker entirely.unknownforces you to prove the type before use. Exception: third-party libraries without types and explicit dynamic-data boundaries (e.g. JSON parse at the API edge), with a comment explaining why.// Wrong function parse(data: any) { return data.name; } // Correct function parse(data: unknown): string { if (typeof data === 'object' && data !== null && 'name' in data) { return String((data as { name: unknown }).name); } throw new Error('Invalid data shape'); } -
AVOID:
Objector{}as a type. Both accept nearly everything. UseRecord<string, unknown>for arbitrary objects or define an explicit interface.// Wrong function merge(a: {}, b: Object): {} { ... } // Correct function merge<T extends Record<string, unknown>>(a: T, b: Partial<T>): T { ... } -
SHOULD: Use
asonly when you know more than the compiler — and document why. Prefer type guards orsatisfiesinstead.// Wrong — silences the error, hides the bug const user = response.data as User; // Correct — validate first function isUser(v: unknown): v is User { return typeof v === 'object' && v !== null && 'id' in v && 'email' in v; } const user = isUser(response.data) ? response.data : null; -
SHOULD: Mark immutable data
readonly. Prevents accidental mutation and communicates intent.// Avoid function process(ids: string[]) { ids.push('extra'); } // Prefer function process(ids: readonly string[]) { /* ids.push() is a compile error */ } -
MUST: Enable
strictNullChecksand handle everyT | undefined. Optional chaining?.returnsundefined— always handle that branch.// Wrong const name = user?.profile.name.toUpperCase(); // crashes if name is undefined // Correct const name = user?.profile.name?.toUpperCase() ?? 'Anonymous'; -
SHOULD: Use branded types for IDs. Prevents passing a
UserIdwhere anOrderIdis expected — both arestringat runtime.type UserId = string & { readonly _brand: 'UserId' }; type OrderId = string & { readonly _brand: 'OrderId' }; function createUserId(raw: string): UserId { return raw as UserId; } function getUser(id: UserId): User { ... } // getUser(orderId) → compile error
Patterns
-
SHOULD: Use discriminated unions for state, not optional fields. Optional fields force you to reason about all combinations. A discriminated union makes illegal states unrepresentable.
// Avoid — 8 possible combinations, most invalid type Request = { loading?: boolean; data?: User; error?: Error }; // Prefer — exactly 3 valid states type Request = | { status: 'idle' } | { status: 'loading' } | { status: 'success'; data: User } | { status: 'error'; error: Error }; -
SHOULD: Use
satisfiesto validate shape without widening the type.as constpreserves literals;satisfiesvalidates against an interface without losing them.const config = { host: 'localhost', port: 5432, } satisfies DatabaseConfig; // config.port is still typed as 5432, not number -
SHOULD: Use
constobjects instead ofenum. Enums emit runtime code, have surprising reverse-mapping behavior, and are not idiomatic TypeScript.// Avoid enum Direction { Up, Down, Left, Right } // Prefer const Direction = { Up: 'Up', Down: 'Down', Left: 'Left', Right: 'Right' } as const; type Direction = typeof Direction[keyof typeof Direction]; -
AVOID: Barrel
index.tsre-exports in large modules. They cause circular dependency chains that are hard to debug. Export directly from source files or use explicit named re-exports only.// Wrong — index.ts re-exports everything, A imports B through index, B imports A through index export * from './userService'; export * from './orderService'; // Correct — import directly import { getUser } from './services/userService';
Error Handling
-
MUST: Type your thrown errors explicitly.
catch (e)gives youunknownin strict mode. Narrow before accessing properties.try { await fetchUser(id); } catch (e) { // Wrong: e.message — e is unknown // Correct: const message = e instanceof Error ? e.message : String(e); logger.error('fetchUser failed', { message, id }); } -
SHOULD: Use
Resulttypes for expected failures instead of throwing. Throwing for control flow forces callers to know which functions throw and what.type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E }; function parseConfig(raw: string): Result<Config> { try { return { ok: true, value: JSON.parse(raw) }; } catch (e) { return { ok: false, error: e instanceof Error ? e : new Error(String(e)) }; } }
Performance
-
MUST: Use
Promise.allfor independent async operations, not sequentialawait. Sequential awaits multiply latency.// Wrong — 300ms total if each takes 100ms const user = await getUser(id); const orders = await getOrders(id); const prefs = await getPrefs(id); // Correct — 100ms total const [user, orders, prefs] = await Promise.all([ getUser(id), getOrders(id), getPrefs(id), ]); -
SHOULD: Use
Promise.allSettledwhen partial failure is acceptable.Promise.allrejects on the first failure.allSettledcollects all results.const results = await Promise.allSettled(ids.map(fetchItem)); const succeeded = results .filter((r): r is PromiseFulfilledResult<Item> => r.status === 'fulfilled') .map(r => r.value);
Testing
-
MUST: Type test helpers and mocks — never use
as anyto silence mock errors. Untyped mocks let type errors hide until runtime. Exception: prototyping spikes that are explicitly thrown away before merge.// Wrong const mockUser = { id: '1' } as any; // Correct const mockUser: User = { id: createUserId('1'), email: '[email protected]', name: 'Alice' }; -
SHOULD: Test the discriminated union branches explicitly. Each
statusvariant is a separate code path. One test per branch minimum.it('renders error state', () => { const state: Request = { status: 'error', error: new Error('timeout') }; render(<RequestView state={state} />); expect(screen.getByRole('alert')).toHaveTextContent('timeout'); }); -
SHOULD: Use
expectTypeOforassertTypefor type-level tests. Runtime tests cannot catch type regressions.import { expectTypeOf } from 'vitest'; expectTypeOf(createUserId('x')).toEqualTypeOf<UserId>();
Why This Sub-Skill Earns Stars
These rules target the gap between "TypeScript that compiles" and "TypeScript that is safe". LLMs default to any, skip discriminated unions, and reach for as assertions because they are the path of least resistance. Each rule here closes a specific escape hatch that lets type errors reach production. The MUST/SHOULD/AVOID classification means safety-critical rules are strict and stylistic rules respect context.