.claude/skills/typescript-strict/SKILL.md
TypeScript strict mode patterns including schema-first development, branded types, type vs interface guidance, and tsconfig strict flags. Use when writing TypeScript code, defining types or schemas, or reviewing type safety. For immutability and pure function patterns, see the functional skill.
npx skillsauth add jscriptcoder/jshack.me typescript-strictInstall 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.
any - ever. Use unknown if type is truly unknownas Type) without justificationtype over interface for data structuresinterface for behavior contracts onlytype — for data structuresexport type RemoteMachine = {
readonly ip: string;
readonly hostname: string;
readonly ports: ReadonlyArray<Port>;
readonly users: ReadonlyArray<RemoteUser>;
};
Why type? Better for unions, intersections, mapped types. readonly signals immutability. More flexible composition with utility types.
interface — for behavior contractsexport interface FileSystemAdapter {
getNode(machineId: string, path: string): FileNode | undefined;
writeFile(machineId: string, path: string, content: string): void;
}
Why interface? Signals "this must be implemented." Works with implements keyword. Conventional for dependency injection.
Define schemas once, import everywhere. Never duplicate the same validation logic across multiple files.
// ✅ Define once
export const GameStateSchema = z.object({
seed: z.string().min(1),
workstationName: z.string().min(1),
username: z.string().min(1),
rootPassword: z.string().min(1),
});
export type GameState = z.infer<typeof GameStateSchema>;
// Import and use wherever needed
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noPropertyAccessFromIndexSignature": true,
"forceConsistentCasingInFileNames": true,
"allowUnusedLabels": false
}
}
Core strict flags:
strict: true - Enables all strict type checking optionsnoImplicitAny - Error on expressions/declarations with implied any typestrictNullChecks - null and undefined have their own types (not assignable to everything)noUnusedLocals - Error on unused local variablesnoUnusedParameters - Error on unused function parametersnoImplicitReturns - Error when not all code paths return a valuenoFallthroughCasesInSwitch - Error on fallthrough cases in switch statementsAdditional safety flags (CRITICAL):
noUncheckedIndexedAccess - Array/object access returns T | undefined (prevents runtime errors from assuming elements exist)exactOptionalPropertyTypes - Distinguishes property?: T from property: T | undefined (more precise types)noPropertyAccessFromIndexSignature - Requires bracket notation for index signature properties (forces awareness of dynamic access)forceConsistentCasingInFileNames - Prevents case sensitivity issues across operating systemsallowUnusedLabels - Error on unused labels (catches accidental labels that do nothing)@ts-ignore without explicit comments explaining whyThe noUnusedParameters rule can reveal architectural problems:
Example: A function with an unused parameter often indicates the parameter belongs in a different layer. Strict mode catches these design issues early.
For detailed patterns on immutability (readonly, ReadonlyArray), pure functions, composition, Result types, array methods, and factory functions, see the functional skill. These are the canonical patterns used across the codebase.
Key TypeScript-specific notes:
readonly on all type properties and ReadonlyArray<T> for arraysreadonly is used — leverage this// IndexedDB data, user input, external data
const FileSystemPatchSchema = z.object({
machineId: z.string(),
path: z.string().startsWith('/'),
content: z.string().nullable(),
owner: z.enum(['root', 'user', 'guest']),
});
type FileSystemPatch = z.infer<typeof FileSystemPatchSchema>;
// Validate at boundary
const patch = FileSystemPatchSchema.parse(storedData);
Partial<T>, Pick<T>, etc.)// ✅ CORRECT - No schema needed
type PermissionResult = { allowed: true } | { allowed: false; reason: string };
// ✅ CORRECT - Interface, no validation
interface CommandExecutor {
execute(args: ReadonlyArray<string>): string | SpecialOutput;
}
For type-safe primitives:
type MachineIp = string & { readonly brand: unique symbol };
type MissionSeed = string & { readonly brand: unique symbol };
// Type-safe at compile time
const connectToMachine = (ip: MachineIp, seed: MissionSeed) => {
// Implementation
};
// ❌ Can't pass raw string
connectToMachine('10.0.1.5', 'abc123'); // Error
// ✅ Must use branded type
const ip = '10.0.1.5' as MachineIp;
const seed = 'abc123' as MissionSeed;
connectToMachine(ip, seed); // OK
When writing TypeScript code, verify:
any types - using unknown where type is truly unknowntype for data structures with readonlyinterface for behavior contractsfunctional skilldevelopment
Testing patterns for behavior-driven tests. Use when writing tests, creating test factories, structuring test files, or deciding what to test. Do NOT use for UI-specific testing (see front-end-testing or react-testing skills).
testing
Evaluates test quality using Dave Farley's 8 properties. Use when reviewing tests, assessing test suite quality, or analyzing test effectiveness against TDD best practices.
development
Test-Driven Development workflow. Use for ALL code changes - features, bug fixes, refactoring. TDD is non-negotiable.
development
Refactoring assessment and patterns. Use after mutation testing validates test strength (MUTATE phase) to assess improvement opportunities.