AI refac
Getting Started

بِسْمِ ٱللَّهِ ٱلرَّحْمَـٰنِ ٱلرَّحِيمِ

Usage

Move files and folders, rename symbols, preview a change, and run many changes in one call.

This page covers the commands you need every day: move, rename, preview, and many changes in one call.

Move a file

refac move \
  --project-path /path/to/project \
  --source-path src/old/module.ts \
  --target-path src/new/module.ts

--source-path and --target-path may be absolute or relative to --project-path. A target folder that does not exist yet is created. The language comes from the file extension.

--project-path is the package root of the language, not the monorepo root:

Language--project-path
TypeScript / JavaScriptThe folder with the tsconfig.json that includes every caller
Kotlin / AndroidThe Gradle root, the folder with settings.gradle.kts
GoThe folder with go.mod
RustThe folder with Cargo.toml
PythonThe project folder
DartThe package folder with pubspec.yaml
MarkdownAny folder. Links in every Markdown file below it are updated

Without --project-path, relative paths are taken from the current directory. To skip the flag on every call, set REFAC_PROJECT_PATH:

export REFAC_PROJECT_PATH=/path/to/project
refac move --source-path src/old.ts --target-path src/new.ts

Move a folder

TypeScript / JavaScript, Kotlin, and Markdown move folders. A Markdown folder may hold only Markdown files and assets.

refac move --project-path /my/docs --source-path guides --target-path handbook/guides

Go, Rust, Python, and Dart need individual files. A Go file takes its whole package with it, and a Rust module moves with move-module.

Preview first

Add --dry-run to any move or rename. refac plans the change, prints it, and writes nothing:

refac move --dry-run --project-path /my/docs --source-path guides --target-path handbook/guides
// Dry run: nothing was changed. 1 path would move, with 4 edits to update.

// Markdown results:

guide -> handbook/guide
// README.md (3 edits)
// guide/setup.md (1 edit)

// Note: Checked 2 Markdown files; updated 4 links in 2 files.

// Run the same command without --dry-run to apply the move.

There is one old -> new line per path that would move and one // file (N edits) line per file that would be edited. An edit is one rewritten import, module line, package line, or link. A dry run runs the same checks as the real command and refuses what it refuses, with exit code 1 and the same report. The real command will inshallah do what the preview showed.

Language servers start as for a real command and stop afterwards, so a preview costs about the same time. Python (Rope) and Kotlin cannot plan without moving, so they run the real move on a throw-away copy of the project and report the difference. The copy is limited to 500 MiB (REFAC_DRY_RUN_COPY_MAX_MB).

Move several things in one call

Repeat the flags. The first source goes to the first target, the second to the second, and so on. One call may mix languages:

refac move --project-path /my/project \
  --source-path src/a.ts --source-path docs/a.md \
  --target-path src/x.ts --target-path docs/x.md

refac groups the files by language and moves each group in one go, with one language server per group, so one call will inshallah save you a server start for every extra file. For a long list, write it once as JSON:

[
  {"source_path": "src/a.ts", "target_path": "src/pkg/a.ts"},
  {"source_path": "src/b.ts", "target_path": "src/pkg/b.ts"}
]
refac move --project-path /my/project --batch moves.json
cat moves.json | refac move --project-path /my/project --batch -

Each entry has exactly those two keys, and paths are read as the flags are. An empty list, or a file of any other shape, is an error that names the file. --batch cannot be combined with --source-path or --target-path. Check the list with --dry-run, keep it, and run it again after a fix.

Inside one language group a move is all or nothing. Across groups it is not: if the Go group fails after the Markdown group moved, the Markdown files stay moved. The report then starts with // Partly done:, lists the failed group under // Failed:, and the exit code is 1.

Rename a symbol

refac rename \
  --project-path /path/to/package \
  --file src/lib/util.ts \
  --symbol total \
  --new-name grandTotal

refac rename works for TypeScript / JavaScript, Kotlin, Go, Rust, Python, and Dart. The extension of --file picks the language, and a language server finds the real references, so a different symbol with the same name is left alone and a symbol imported under another name is still found. With --dry-run the output looks like this:

// Dry run: nothing was changed. Planned and verified rename:
total -> grandTotal
// src/core/util.ts (1 edit)
// src/main.ts (2 edits)
// 3 edit(s) in 2 file(s).
// Run the same command without --dry-run to write these edits.

If the name means several symbols in the file, for example a local count in two functions, nothing is renamed and the error lists every candidate with its line. Choose one with --line (1-based) and, when the line holds the name twice, --column (1-based byte column, like rg --column).

A new name that clashes with another declaration, or that shadows one, is refused with the places that would change. The server is shown the renamed text in memory and must find exactly the same places again before anything is written, so a rename that changes what your code means inshallah won't reach the disk.

Several renames at once

[
  {"file": "src/a.rs", "symbol": "old_a", "new_name": "new_a"},
  {"file": "src/b.rs", "symbol": "old_b", "new_name": "new_b", "line": 12}
]
refac rename --project-path /my/crate --batch renames.json

The language server starts once for the whole list. Entries run in order and each one sees the files as the one before left them, so a later entry may use a name that an earlier entry gave. The batch is all or nothing: when entry 3 of 5 fails, entries 1 and 2 are taken back and the error names entry 3. One language per batch, TypeScript included. A path in an entry must exist when the batch runs, so rename symbols first and move files afterwards.

Move a Rust module

refac move-module --project-path /my/workspace \
  crate::engine::matching crate::domain::matching

Both arguments start with crate:: and name modules of the same crate. The module moves with its children and every use, crate::, super::, and self:: path that reached it is rewritten. The workspace is checked with Cargo afterwards and should inshallah still compile. See the Rust page for the rules.

Read the result

Lines that start with // are refac's report, and the lines without it are the moved or renamed things. A // Note: line names a place that refac did not change and that you may need to change yourself: a name inside a string or comment, a call on an untyped receiver, a ProGuard rule, a Rust macro_rules! body. Every note ends with the rg -w command that finds the rest. The full vocabulary is on the Output page.

JSON output

refac move --json \
  --project-path /path/to/project \
  --source-path src/old.go \
  --target-path pkg/new/old.go

With --json a command prints one JSON document on stdout. A move returns status, operation, project_path, source_path, target_path, and result, and a dry run adds dry_run, moves, files, edits, and notes. A failure prints {"status":"error","error":"..."} on stderr instead. The shapes of all commands are on the Output page.

Exit codes

CodeMeaning
0Everything succeeded
1Something failed or nothing was moved. stderr says why and whether files were changed
2The command line itself was wrong. The usage is printed

After a change

Run your project's own build and tests. git diff shows exactly what refac wrote. The build should inshallah pass, and for the places a language server cannot see, the // Note: lines tell you where to look.

Language-specific rules, limits, and costs are on the language pages and in Capabilities and limits.

On this page