Agent skill
type-hardening
Incrementally improve type safety by replacing string literals with enums, narrowing `any` types, and using shared types. Works in small verified batches. Preserves functionality while improving code quality.
Install this agent skill to your Project
npx add-skill https://github.com/BerryKuipers/claude-code-toolkit/tree/main/.claude/skills/quality/type-hardening
SKILL.md
Type Hardening Skill
Purpose
Gradually improve type safety by:
- Replacing magic strings with enums/constants
- Narrowing
any/unknownto specific types - Using shared types instead of inline definitions
- Adding type imports where missing
When to Use
- Code review finds string literals that should be enums
anytypes that can be narrowed- Inline types duplicating shared definitions
- Post-refactoring type cleanup
- Technical debt reduction sprints
- Triggered by
/harden-typescommand or/refactor --strategy=types
Core Principles
- Check Existing First: Always search for existing types before creating new ones
- Small Batches: 1-3 related changes at a time
- Verify Immediately: Type check after every change
- No Logic Changes: Purely structural/type improvements
- Commit Often: Atomic commits per successful batch
- New Types Allowed: Create new types when genuinely needed for clean code
- No
unknownCheating: Replacinganywithunknownis NOT an improvement - narrow to a SPECIFIC type or leave it (with a TODO comment if needed)
Instructions
Step 1: Discover Existing Types
Before ANY changes, locate existing type definitions:
# Find Prisma enums (common source of types)
grep -E "^enum " backend/prisma/schema.prisma 2>/dev/null || true
# Find shared types
find . -path ./node_modules -prune -o -name "*.types.ts" -print 2>/dev/null
find . -path ./node_modules -prune -o -name "types.ts" -print 2>/dev/null
# Find constants files
find . -path ./node_modules -prune -o -name "*.constants.ts" -print 2>/dev/null
# Common locations to check:
# - shared/types/
# - src/types/
# - backend/src/types/
# - backend/src/generated/prisma/ (Prisma enums)
Record findings before proceeding.
Step 2: Analyze Target File
Read the target file and identify:
### Type Hardening Opportunities
1. **String Literals** → Should use enum/constant
- Line 45: `'admin'` → `UserRole.admin`
- Line 89: `'pending'` → `JobStatus.PENDING`
2. **Any Types** → Should be narrowed to SPECIFIC types
- Line 23: `any` → `UserWithRole` ✅
- Line 67: `any` → `ApiResponse<T>` ✅
- ❌ WRONG: `any` → `unknown` (this is cheating, not fixing)
3. **Inline Types** → Should use shared
- Line 102: `{ id: string; name: string }` → `UserBasic`
4. **Missing Imports** → Need to add
- `UserRole` from `@prisma/client` or generated types
Step 3: Prioritize Changes
Work in this order for safest progression:
-
Prisma Enums First (most reliable)
typescript// ❌ BEFORE if (user.role === 'admin') // ✅ AFTER import { UserRole } from '../generated/prisma/index.js'; if (user.role === UserRole.admin) -
Shared Types Second (well-established)
typescript// ❌ BEFORE type NotificationType = 'info' | 'success' | 'warning' | 'error'; // ✅ AFTER import type { NotificationType } from '@shared/types/notification'; -
Constants Third (project-specific)
typescript// ❌ BEFORE const provider = 'openai'; // ✅ AFTER import { AI_PROVIDERS } from '../types/ai.constants'; const provider = AI_PROVIDERS.OPENAI; -
New Types Last (when genuinely needed)
typescript// Only create new types when: // - No existing type serves the purpose // - The type will be used in multiple places // - It improves code clarity significantly
Step 4: Apply Changes (Batch of 1-3)
# Edit the file with precise changes
# Use Edit tool for exact string replacement
Example transformation:
// Before (lines 44-48)
async function promoteUser(userId: string): Promise<void> {
const user = await userRepo.findById(userId);
if (user.role === 'user') {
await userRepo.update(userId, { role: 'admin' });
}
}
// After
import { UserRole } from '../generated/prisma/index.js';
async function promoteUser(userId: string): Promise<void> {
const user = await userRepo.findById(userId);
if (user.role === UserRole.user) {
await userRepo.update(userId, { role: UserRole.admin });
}
}
Step 5: Verify Immediately
# Backend files
cd backend && npx tsc --noEmit
# Frontend files
npx tsc --noEmit
# If using ESLint
npx eslint <modified-file>
If verification fails:
- ❌ DO NOT proceed to next batch
- Analyze the error
- Fix or revert the change
- Re-verify before continuing
Step 6: Commit Atomic Change
git add <modified-file>
git commit -m "refactor: harden types in <filename>
- Replace string literals with <EnumName>
- Narrow any to <TypeName>
- No behavior change
Co-Authored-By: Claude <noreply@anthropic.com>"
Step 7: Iterate or Complete
Continue if:
- More hardening opportunities exist
- User requested multiple files
- Within iteration limit
Stop if:
- File is clean (no more opportunities)
- Verification failed (fix first)
- User requested stop
- Iteration limit reached
Output Format
Progress Report
## Type Hardening Report: `backend/src/services/auth.service.ts`
### Changes Applied
| Line | Before | After | Status |
|------|--------|-------|--------|
| 45 | `'admin'` | `UserRole.admin` | ✅ |
| 89 | `'user'` | `UserRole.user` | ✅ |
| 123 | `any` | `AuthPayload` | ✅ |
### Verification
- TypeScript: ✅ 0 errors
- Lint: ✅ passed
### Remaining Opportunities
- Line 156: `'pending'` could use `JobStatus.PENDING`
- Line 201: `unknown` could narrow to `TokenPayload`
### Next Action
Continue? (y/n/skip to file X)
Integration Points
With Refactor Agent
Referenced as Pattern 6 in /refactor:
### Pattern 6: Type Hardening
**When**: String literals, `any` types, inline types matching shared
**How**: Use `type-hardening` skill
**Reference**: `.claude/skills/quality/type-hardening/SKILL.md`
With Quality Gate
Can be part of quality checks:
### Optional: Type Strictness Check
Use `type-hardening` skill in analysis-only mode to identify
opportunities without making changes.
With Gemini Delegation
Safe for delegation with guardrails:
## Gemini Handoff: Type Hardening
Scope: [specific files]
Task: Replace string literals with existing enums
Verification: tsc --noEmit must pass
Common Type Sources
| Source | Location | Example |
|---|---|---|
| Prisma Enums | backend/src/generated/prisma/ |
UserRole, JobStatus |
| Shared Types | shared/types/ |
NotificationType, ApiResponse |
| Domain Types | src/types/ |
UserWithRole, SceneData |
| Constants | */constants.ts |
AI_PROVIDERS, CACHE_TTL |
Error Recovery
Import Resolution Failed
# Check if type is exported
grep "export.*TypeName" shared/types/
# Check tsconfig paths
cat tsconfig.json | grep -A5 "paths"
Type Mismatch After Change
# Revert the change
git checkout -- <file>
# Analyze why it failed
# - Was the enum value different? (admin vs ADMIN)
# - Was the type path wrong?
# - Was there a missing export?
Best Practices
- Never guess - Always verify the type exists before using it
- Match casing -
UserRole.adminnotUserRole.ADMIN(check actual enum) - Preserve semantics -
'admin'becomesUserRole.admin, notRole.ADMIN - Import correctly - Use
import typefor type-only imports - Test critical paths - Extra verification for auth/security code
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
validate-typescript
Run TypeScript compiler type-checking (tsc --noEmit) to validate type safety and catch type errors. Returns structured output with error counts, categories (type/syntax/import errors), and affected files. Used for quality gates and pre-commit validation.
validate-coverage-threshold
Validate test coverage meets minimum thresholds (default 80% overall, 80% statements, 75% branches, 80% functions). Parses coverage reports from coverage/coverage-summary.json or test output. Returns pass/fail status with detailed metrics and identifies uncovered files.
quality-gate
Comprehensive quality validation for TypeScript/JavaScript projects - runs TypeScript checks, tests, coverage analysis, build validation, and linting with structured JSON results
run-comprehensive-tests
Execute comprehensive test suite (Vitest/Jest) with coverage reporting and failure analysis. Returns structured output with test counts (total/passed/failed), coverage percentage, duration, and detailed failure information. Used for quality gates and CI/CD validation.
validate-build
Run production build validation (npm run build, vite build, tsc) to ensure code compiles and builds successfully. Returns structured output with build status, duration, size metrics, and error details. Used for quality gates and deployment readiness checks.
validate-lint
Run ESLint and Prettier validation to check code style, formatting, and best practices. Returns structured output with error/warning counts, rule violations, and affected files. Used for code quality gates and pre-commit validation.
Didn't find tool you were looking for?