Incrementally improve type safety by replacing string literals with enums, narrowing any types, and using shared types. Works in small verified batches...
Gradually improve type safety by:
any/unknown to specific typesany types that can be narrowed/harden-types command or /refactor --strategy=typesunknown Cheating: Replacing any with unknown is NOT an improvement - narrow to a SPECIFIC type or leave it (with a TODO comment if needed)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.
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
Work in this order for safest progression:
Prisma Enums First (most reliable)
// ❌ BEFORE
if (user.role === 'admin')
// ✅ AFTER
import { UserRole } from '../generated/prisma/index.js';
if (user.role === UserRole.admin)
Shared Types Second (well-established)
// ❌ BEFORE
type NotificationType = 'info' | 'success' | 'warning' | 'error';
// ✅ AFTER
import type { NotificationType } from '@shared/types/notification';
Constants Third (project-specific)
// ❌ BEFORE
const provider = 'openai';
// ✅ AFTER
import { AI_PROVIDERS } from '../types/ai.constants';
const provider = AI_PROVIDERS.OPENAI;
New Types Last (when genuinely needed)
// Only create new types when:
// - No existing type serves the purpose
// - The type will be used in multiple places
// - It improves code clarity significantly
# 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 });
}
}
# Backend files
cd backend && npx tsc --noEmit
# Frontend files
npx tsc --noEmit
# If using ESLint
npx eslint <modified-file>
If verification fails:
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>"
Continue if:
Stop if:
## 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)
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`
Can be part of quality checks:
### Optional: Type Strictness Check
Use `type-hardening` skill in analysis-only mode to identify
opportunities without making changes.
Safe for delegation with guardrails:
## Gemini Handoff: Type Hardening
Scope: [specific files]
Task: Replace string literals with existing enums
Verification: tsc --noEmit must pass
| 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 |
# Check if type is exported
grep "export.*TypeName" shared/types/
# Check tsconfig paths
cat tsconfig.json | grep -A5 "paths"
# 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?
UserRole.admin not UserRole.ADMIN (check actual enum)'admin' becomes UserRole.admin, not Role.ADMINimport type for type-only imports