patchwork
AST-native sed — find, replace, delete, and insert code by structure, not regex.
Installation
cargo install patchwork-cli
Claude Code Plugin
Install as a Claude Code plugin for AI-assisted code transformations:
/plugin marketplace add ThatXliner/claude-plugins
/plugin install patchwork
Usage
# Rename a method across files without false positives
patchwork replace -i -p 'getOldData($a)' -r 'getData($a)' src/**/*.java
# Replace a logging framework
patchwork delete -i -p 'logger.debug($msg)' src/*.py
patchwork insert-before -p 'logger.debug($msg)' --code 'tracing.debug($msg)' src/*.py
# Match by structure, not regex
patchwork find -p 'return null;' src/
More examples are available in the examples directory.
Motivation
Most code transformation tools sit at extremes. Regex-based tools (sed) are fragile across multi-line patterns and nested syntax. Full-featured linters (semgrep, ast-grep) require config files, YAML rules, or heavy runtimes — overkill for the common case of "find this pattern, change it."
patchwork is for the middle ground: structural search and replace that runs as a single find | xargs pipeline. Zero config, zero setup, one 3MB binary.
How it compares
ast-grep is more mature, supports more languages, has YAML rules, LSP, VS Code extension, MCP server, playground, pre-commit hooks, language bindings — the works. patchwork doesn't try to catch up on that axis. They go by different design philosophies.
| Tool | Language | Parsing | Size | Languages | Maturity |
|---|
| patchwork | Rust | tree-sitter AST | ~5MB | 13 | Alpha — one person, <1yr |
| ast-grep | Rust | tree-sitter AST | ~10MB | 25+ | Mature — 174 releases, active community |
| Comby | OCaml | parser-free | ~8MB | ~all | Mature |
| Semgrep | Python | real parsers | 200MB+ | 20+ | Mature — enterprise security product |
patchwork is built for scripts. Default output is intentionally boring (just file:line:col) because it's trivial to cut, xargs, or pipe into other tools. No config files, no YAML, no rule system — five flat commands with sed-like flags. If the only thing between your find and your edit is a tool that needs a config file, you reach for something else.
patchwork aims for more expressive matching. The Rust-style repetition syntax is the first step. We have $BODY/$STMT/$EXPR special tokens, a dedicated insert-before/insert-after command, and more matching logic planned — things that go beyond what a YAML rule system enables.
So why does patchwork exist?
Two reasons:
-
The Rust-style repetition makes this tool similarly powerful as regex. $f($($arg,)*) captures the separator and handles zero/one/many args with a natural Rust macro-like syntax. ast-grep's console.log($$$ARGS) has no separator awareness — if you delete a single arg, you're left with trailing commas.
-
Vision: a tool that AI agents reach for first. patchwork aims to be the simplest possible AST editor — so simple that an LLM can generate precise patchwork commands without thinking about YAML config, rule composition, or scanning modes. Whether it achieves this better than ast-grep -p '...' -r '...' is an open question, but the design surface is intentionally tiny.
Realistically: if you need a mature, well-documented tool today, use ast-grep. If you're interested in the repetition syntax experiment or want to influence the design of a simpler alternative, watch this space.
How it works
Write a code snippet and patchwork finds structurally identical code in your source.
Names and values match exactly by default — return 1; only matches return 1;, not return 42;. This means you don't accidentally match code that happens to have the same shape but different identifiers.
# Only matches exactly this call
patchwork find -p 'old_api(x)' src/
# Use $ to match any identifier
patchwork find -p 'old_api($x)' src/
$ placeholders
$name matches any single AST node — any identifier, literal, or expression. It's like .* for code but structure-aware.
# Match any call to old_api with any single argument
patchwork replace -i -p 'old_api($arg)' -r 'new_api($arg)' src/**/*.java
# Match any two-argument call, reorder args in replacement
patchwork replace -i -p '$f($a, $b)' -r '$f($b, $a)' src/*.py
# Delete all calls to debug regardless of argument
patchwork delete -i -p 'debug($msg)' src/*.py
# Match any return statement
patchwork find -p 'return $val;' src/
Type-constrained placeholders: $name:Kind