Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Regex mode

Default mode. Powered by the regex crate, so the syntax is Perl-compatible minus lookaround (catastrophic backtracking is gone too — regex is linear-time).

Multi-line by default

. matches \n by default (implicit (?s)); --single-line (-s) turns that off. This matches what LLMs usually expect.

Capture interpolation

recast 'fn (\w+)_old\b' 'fn ${1}_new' src/

$1, ${name} interpolated. To treat the pattern and replacement as literal text, pass --literal (-L).

Case-insensitive

recast -i 'todo' 'TODO' .

Match-count guard

--at-least N (default 1) and --at-most N (default unbounded) bracket the total matches across all files. Violations exit 2.

recast --at-least 5 'foo' 'bar' src/       # require ≥5 matches
recast --at-most 0 'TODO' 'FIXME' src/     # CI gate: there must be zero TODOs
recast --at-least 0 'maybe' 'def' src/     # allow zero matches (no guard)

Idempotency check

The plan step reapplies the pattern to its own post-image. If any file would change again, recast aborts with non_convergent — the rewrite isn’t safe to run twice. Override with --allow-non-convergent if you know what you’re doing.

Examples of patterns recast rejects:

  • 'a' -> 'aa' (grows on every run)
  • 'foo' -> 'foofoo'

Examples it accepts:

  • 'old' -> 'new'
  • 'fn (\w+)_old' -> 'fn ${1}_new'

Filters

recast -t rust 'Old' 'New' .                # only Rust files
recast -T markdown 'Old' 'New' .            # everything except Markdown
recast -g '!vendor/**' 'Old' 'New' .        # exclude vendor dir
recast --no-ignore 'Old' 'New' .            # bypass .gitignore
recast --hidden 'Old' 'New' .               # include dot-files
recast --max-bytes 102400 'Old' 'New' .     # skip files > 100KiB
recast --max-files 50 'Old' 'New' .         # cap total file count

-t / -T accept the same shorthand vocabulary as ripgrep (rust, js, py, markdown, …). -g accepts ripgrep-style include/exclude globs.

Stdin mode

echo 'fn old_name() {}' | recast --stdin 'old_name' 'new_name'
# fn new_name() {}

Read one buffer, rewrite once, write to stdout. Skips the walker and commit phases. The match-count guard still applies.