| name | refactor |
| description | Code refactoring patterns - extract, split, restructure without changing behavior. |
| triggers | ["refactor","extract","split","restructure"] |
| allowed-tools | Bash, Read, Write, Edit, Grep, Glob |
| model | opus |
| user-invocable | true |
| argument-hint | [target file or pattern] |
Refactoring Patterns
Rule #1: Tests pass before AND after. No behavior change.
When to Refactor
| Signal | Refactoring |
|---|
| File > 300 lines | Split into modules |
| Component > 200 lines | Extract sub-components |
| Function > 50 lines | Extract helpers |
| 3+ similar blocks | Extract shared utility |
| Prop drilling > 3 levels | Context or composition |
| God object/file | Single responsibility split |
Pattern: Split Large File
Before: piapi.ts (1240 lines)
After:
piapi/
├── index.ts (barrel export)
├── client.ts (base client, auth)
├── music.ts (music generation)
├── image.ts (image generation)
└── types.ts (shared types)
Steps:
- Identify logical groups (by domain, not by size)
- Create module directory with
index.ts barrel
- Move code group by group, fixing imports
npm run typecheck after each move
- Barrel export preserves existing import paths
export { PiAPIClient } from './client'
export { generateMusic, extendSong } from './music'
export { generateImage } from './image'
export type { MusicParams, ImageParams } from './types'
Pattern: Extract Component
export default function LibraryPage() {
}
export default function LibraryPage() {
const [filters, setFilters] = useState(defaultFilters)
const songs = useSongs(filters)
return (
<div>
<FilterBar filters={filters} onChange={setFilters} />
<BulkActions selected={selected} />
<SongList songs={songs} />
<Pagination total={songs.total} />
</div>
)
}
Rules:
- Each component gets its own file
- Parent passes data down, children emit events up
- Shared state stays in parent or context
- Co-locate related components in same directory
Pattern: Extract Hook
function SongPlayer() {
const [playing, setPlaying] = useState(false)
const [progress, setProgress] = useState(0)
const audioRef = useRef<HTMLAudioElement>(null)
useEffect(() => {
const audio = audioRef.current
if (!audio) return
const update = () => setProgress(audio.currentTime / audio.duration)
audio.addEventListener('timeupdate', update)
return () => audio.removeEventListener('timeupdate', update)
}, [])
return <div>...</div>
}
function SongPlayer() {
const { playing, progress, toggle, seek } = useAudioPlayer(songUrl)
return <div>...</div>
}
Pattern: Replace Prop Drilling
<App user={user}>
<Layout user={user}>
<Sidebar user={user}>
<UserAvatar user={user} />
// After: context
const UserContext = createContext<User | null>(null)
function App() {
return (
<UserContext.Provider value={user}>
<Layout><Sidebar><UserAvatar /></Sidebar></Layout>
</UserContext.Provider>
)
}
function UserAvatar() {
const user = useContext(UserContext)
// ...
}
Pattern: Consolidate Duplicates
async function fetchSongs() { }
async function fetchArtists() { }
async function fetchAlbums() { }
async function fetchFromSupabase<T>(
table: string,
query?: SupabaseQuery
): Promise<T[]> {
const { data, error } = await supabase
.from(table)
.select(query?.select ?? '*')
.order(query?.orderBy ?? 'created_at', { ascending: false })
.limit(query?.limit ?? 50)
if (error) throw error
return data as T[]
}
Safety Checklist
Before refactoring:
After each step:
After completion:
Integration
| Skill | How It Integrates |
|---|
auto | Refactoring stories executed during auto mode |
brainstorm | Proposes refactoring when large files detected |
review | Flags refactoring opportunities |
design | Refactoring must preserve existing UI (see Preserve UI Structure section) |