| name | type-hardening |
| description | 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. |
Type Hardening Skill
Purpose
Gradually improve type safety by:
- Replacing magic strings with enums/constants
- Narrowing
any/unknown to 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
any types that can be narrowed
- Inline types duplicating shared definitions
- Post-refactoring type cleanup
- Technical debt reduction sprints
- Triggered by
/harden-types command 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
unknown Cheating: Replacing any with unknown is 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:
grep -E "^enum " backend/prisma/schema.prisma 2>/dev/null || true
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 . -path ./node_modules -prune -o -name "*.constants.ts" -print 2>/dev/null
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)
if (user.role === 'admin')
import { UserRole } from '../generated/prisma/index.js';
if (user.role === UserRole.admin)
-
Shared Types Second (well-established)
type NotificationType = 'info' | 'success' | 'warning' | 'error';
import type { NotificationType } from '@shared/types/notification';
-
Constants Third (project-specific)
const provider = 'openai';
import { AI_PROVIDERS } from '../types/ai.constants';
const provider = AI_PROVIDERS.OPENAI;
-
New Types Last (when genuinely needed)
Step 4: Apply Changes (Batch of 1-3)
Example transformation:
async function promoteUser(userId: string): Promise<void> {
const user = await userRepo.findById(userId);
if (user.role === 'user') {
await userRepo.update(userId, { role: 'admin' });
}
}
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
cd backend && npx tsc --noEmit
npx tsc --noEmit
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
grep "export.*TypeName" shared/types/
cat tsconfig.json | grep -A5 "paths"
Type Mismatch After Change
git checkout -- <file>
Best Practices
- Never guess - Always verify the type exists before using it
- Match casing -
UserRole.admin not UserRole.ADMIN (check actual enum)
- Preserve semantics -
'admin' becomes UserRole.admin, not Role.ADMIN
- Import correctly - Use
import type for type-only imports
- Test critical paths - Extra verification for auth/security code