skills/standards-typescript/SKILL.md
Use when editing *.ts or *.tsx files in this project. Enforces TypeScript conventions including Zod-derived types, Prisma type patterns, type-only imports, and shared constant extraction.
npx skillsauth add paulund/ai standards-typescriptInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Always derive TypeScript types from Zod schemas. Never declare the same shape twice.
export const createPostSchema = z.object({ content: z.string().min(1) })
export type CreatePostInput = z.infer<typeof createPostSchema>
Use Prisma.<Model>GetPayload<...> for query result shapes. Never re-declare them.
import type { Prisma } from '@prisma/client'
type Post = Prisma.PostGetPayload<Record<string, never>>
type PostWithProject = Prisma.PostGetPayload<{ include: { project: true } }>
import type { Project, Post } from '@prisma/client'
When the same constant appears in more than one file, extract it to src/lib/ and import it. Client components can import shared constants directly without receiving them as props.
unknown at the boundary and narrow internally with a type guard or Zod parse. Never any.// Good
export interface SocialProvider {
publish(payload: PublishPayload, ctx: PublishContext): Promise<PublishOutcome>
verifyCredentials?(credentials: unknown): Promise<{ handle: string }>
}
// Caller does provider.verifyCredentials?.(...) — no cast.
// Bad — the cast signals the interface boundary is wrong
interface BlueskyCredentialVerifier { verifyCredentials(...): ... }
const provider = registry.get('bluesky') as unknown as BlueskyCredentialVerifier
See ADR 003 — Design Patterns.
as unknown as X Casts Are a Code SmellAny as unknown as X cast is the signal that the interface boundary is wrong. Fix the boundary (add an optional method, introduce a type guard, or narrow the return type) instead of casting. Treat these as a stop and fix item in code review.
Public functions return typed Result unions, not thrown errors:
type Result<T> =
| { ok: true; data: T }
| { ok: false; error: 'rate_limited'; retryAfter: number }
| { ok: false; error: string; retryAfter?: never }
Callers branch on result.ok — TypeScript narrows the union. Throw only for unrecoverable programmer errors.
ReadonlySet for Allowed ValuesWhen validating a string discriminator, expose allowed values as ReadonlySet<PlatformKey> derived from a config object — never hand-maintained:
export const ALLOWED_PLATFORMS: ReadonlySet<PlatformKey> = new Set(
Object.keys(PLATFORM_CONFIGS) as PlatformKey[]
)
export function isPlatformKey(value: unknown): value is PlatformKey {
return typeof value === 'string' && value in PLATFORM_CONFIGS
}
See ADR 003 — Design Patterns.
development
Use when implementing any logic, fixing any bug, or changing any behaviour. Use when you need to prove code works, when a bug report arrives, or when modifying existing functionality. Do NOT use for config changes, data migrations, or dependency updates.
development
Use when starting a new feature, when requirements are unclear, when asked to write code without a clear spec, or before any non-trivial implementation. Do NOT use for trivial bug fixes or one-line changes.
development
Use when you want authoritative, source-cited code free from outdated patterns. Use when building with any framework or library where correctness matters. Detects the stack from dependency files, fetches official documentation, implements following documented patterns, and cites sources for every framework-specific decision.
development
Use when preparing to ship a feature, release, or deployment. Use before merging to main, creating a release, or deploying to production. Do NOT use for CI-only changes or internal refactors that don't reach production.