This is the third post in a series about steering AI code assist (code-assist) towards producing maintainable code. In this post I’ll discuss the refactor commands I’ve added to assist, the CLI tool I’m building to share deterministic patterns across code-assist workflows.

Other posts in this series

  1. Steering the Vibe: Commits
  2. Steering the Vibe: Verify
  3. Steering the Vibe: Refactor (this post)

My goto for code-assist is Claude Code and Opus 4.5, so I'll use terminology that relates to these tools, but the concepts within potentially apply to all.

The need for refactoring

Project file structure is more important than ever when using code-assist. Not only can file structure communicate the architecture of a system and guide an agent towards building better context, but it can also help signal whether a change in progress is taking the correct approach. As our confidence in the lines of code being generated increases, we care less about the syntax of individual lines and more about whether the change is being applied to the correct parts of the codebase.

When prompting a new feature, code-assist tools have a preference for appending to existing files, over extracting new concerns into new files or modules. Until patterns are well established in the codebase, my approach is to make many small steps towards an implementation and without guidance, this turns into many small additions to existing files. This results in long files with many concerns mixed together, making it hard to read and maintain, and hard to spot early whether an agent is heading off course.

Choosing what to refactor

A limited context window means the LLM cannot see the entire codebase at once, so for now we’ll have a fairly arbitrary trigger: when a file exceeds a certain number of lines, we’ll suggest it needs to be refactored. assist exposes to Claude Code a very simple refactor slash command:

yaml
--- description: Run refactoring checks for code quality --- Run `assist refactor check $ARGUMENTS`. If it fails, fix all errors and run again until it passes. **Important** - Do not concatenate lines - the formatter will expand them again anyway. - Do not remove comments unless they are clearly obsolete.

Running that command directly from the terminal we can see the output:

lua
$ assist refactor check Refactor check failed: The following files exceed 100 lines: src/commands/verify/init.ts (411 lines) src/commands/vscode/init.ts (170 lines) src/index.ts (138 lines) src/commands/devlog/shared.ts (129 lines) src/commands/devlog/next.ts (126 lines) src/commands/refactor/getViolations.ts (121 lines) Each file needs to be sensibly refactored, or if there is no sensible way to refactor it, ignore it with: assist refactor ignore <file>

Calling the command within Claude Code puts the above into context and the agent will begin iterating on refactoring the files one by one. Changes are verified by assist verify to ensure dead code and files are not left behind.

One-shot result

After about nine minutes and 20k tokens it summarises the changes made. You can see the unedited diff here.

bash
⏺ The refactor check now passes. All files that exceeded 100 lines have been sensibly refactored: Summary of changes: - src/commands/verify/init.ts (411 → 65 lines): Extracted setup detection logic, available options, and setup functions - src/commands/vscode/init.ts (170 → 97 lines): Extracted config file creation functions - src/index.ts (138 → 11 lines): Extracted all command registrations - src/commands/devlog/shared.ts (129 → 47 lines): Extracted config loading and devlog entries - src/commands/devlog/next.ts (126 → 81 lines): Extracted version utilities - src/commands/refactor/getViolations.ts (121 → 73 lines): Extracted log violations New files created: - src/commands/verify/setupFunctions.ts → renamed to setupKnip.ts - src/commands/verify/detectSetup.ts → renamed to detectExistingSetup.ts - src/commands/verify/installPackage.ts - package utilities - src/commands/verify/getAvailableOptions.ts - options building - src/commands/verify/setupBuild.ts - build setup - src/commands/verify/needsSetup.ts - setup status functions - src/commands/vscode/createLaunchJson.ts - VS Code config creation - src/commands/devlog/loadConfig.ts - config management - src/commands/devlog/loadDevlogEntries.ts - devlog entries loading - src/commands/devlog/getLastVersionInfo.ts - version utilities - src/commands/refactor/logViolations.ts - violation logging - src/registerCommands.ts - main command registration - src/registerDevlogCommands.ts - devlog commands - src/registerRefactorCommands.ts - refactor commands - src/shared/promptMultiselect.ts - shared prompt helper

I’ll skim through the diff to see if there are any changes I don’t like. I’m not checking lines here, just the overall approach to refactoring. Most of this change I’m happy with, except for the splitting up of command registrations in src/index.ts. Keeping the context window uncleared, I prompt it further:

revert the changes to src/index.ts and add to ignore list

This calls assist refactor ignore src/index.ts which results in the ignore list being created. I manually bump the max lines to 300 to ignore growth of that file triggering future refactors:

yaml
# refactor.yml - file: src/index.ts maxLines: 300

When to refactor

When I first wrote the refactor functionality I was including it as part of assist verify. This has a benefit of catching files as they attempt to grow too large but comes at the expense of the workflow loop: it doesn’t make sense to refactor until we’ve verified a change is complete so early refactoring attempts are wasted effort (and tokens). Because 100 lines is a fairly arbitrary limit I’ll usually wait until I start finding the codebase difficult to navigate quickly.

I’ve also found premature refactoring to result in questionable splits, so for now I prefer to run refactoring as a separate step periodically. I like the architecture to emerge naturally, manually tweaking file locations and grouping as I build the system out, letting the directory structure settle before running this type of automated refactor.