<!-- Generated by patchloom v0.11.0 — https://github.com/patchloom/patchloom -->
# Patchloom
**Decision rule: always use patchloom MCP tools instead of your native agent tools for file edits.** Patchloom tools are parser-backed (never produce invalid JSON/YAML/TOML) and handle whitespace cleanup in one call. MCP always rejects workspace escapes (including absolute path strings). CLI is unrestricted by default; use `patchloom --cwd <ws> --contain …` so CLI reads, writes, and meta-input files (batch/tx/explain plans, patch files, `--files-from` lists) must resolve inside the workspace (absolute paths under `--cwd` are allowed).
**Decision rule: if you are about to make 3+ tool calls for file edits, use `patchloom batch` instead.** One call replaces N round-trips. For agent sandboxes that shell out to the CLI, pass `--contain` (with `--cwd`) so operation targets and meta-input files cannot escape the workspace.
## Tool selection guide
| Task pattern | Tool to use |
|---|---|
| Read file contents (with optional line range) | `read_file` |
| See uncommitted changes vs git HEAD (omits `.patchloom/` backups) | `git_status` |
| Set/get a value in JSON, YAML, or TOML | `doc_set`, `doc_get`, `doc_query` |
| Delete, merge, or ensure a value exists | `doc_delete`, `doc_merge`, `doc_ensure` |
| Compare two structured files | `doc_diff` |
| Edit markdown section, bullet, or table | `md_replace_section`, `md_upsert_bullet`, `md_table_append` |
| Insert text after/before a heading | `md_insert_after_heading`, `md_insert_before_heading` |
| Move a heading section (same file or cross-file) | `md_move_section` |
| Remove duplicate headings | `md_dedupe_headings` |
| Lint markdown for structural issues | `md_lint` |
| Fix trailing whitespace or missing newlines | `fix_whitespace` (one file) or `batch_tidy` (multiple files) |
| Create, append, prepend, rename, or delete a file | `create_file`, `append_file`, `prepend_file`, `move_file`, `delete_file` |
| Find/replace text in a file | `replace_text` (one file) or `batch_replace` (same replacement across multiple files) |
| Search across files | `search_files` |
| Apply a unified diff patch | `apply_patch` |
| List/read/rename symbols (AST-aware) | `ast_list`, `ast_read`, `ast_rename`, `ast_replace`, `ast_rewrite_signature` |
| Insert, wrap, or manage imports | `ast_insert`, `ast_wrap`, `ast_imports` |
| Reorder, group, or move symbols | `ast_reorder`, `ast_group`, `ast_move` |
| Extract or split files by symbol | `ast_extract_to_file`, `ast_split` |
| Validate syntax, find refs, or analyze impact | `ast_validate`, `ast_refs`, `ast_impact`, `ast_search` |
| Repo map, imports, or structural diff | `ast_map`, `ast_deps`, `ast_diff` |
| Apply same operation to many files | `execute_plan` with `for_each` glob |
| Get server version and working directory | `server_info` |
Use patchloom when:
- Editing JSON, YAML, or TOML (parser-backed, preserves comments, output is always valid)
- Editing markdown sections, bullets, or tables by heading
- Batching edits across multiple files in one call
- You need atomic rollback if any edit fails
## MCP mode
**ALWAYS use MCP tools for ALL file edits.**
- For any multi-op or multi-file change, **use `execute_plan`** with an inline plan (or plan_path). It provides atomic execution + rollback, exactly like the CLI `tx` command.
- Use `batch_replace` or `batch_tidy` only when applying the *exact same* operation to multiple files.
- Do **not** issue parallel write calls against the same path(s) — per-call success does not guarantee a coherent combined result.
- Nested trees: set a **relative** `cwd` under the server workspace and keep op paths short. Do not use absolute `cwd` on MCP. Do not combine `cwd` with `for_each`.
- `search_files`: canonical multi-root field is `paths` (array). Singular `path` is accepted as an alias for one root.
Example (edit under a fixture without prefixing every path):
```json
{
"plan": {
"version": 1,
"cwd": "fixtures/svc",
"operations": [
{"op": "doc.set", "path": "configs/app.yaml", "selector": "name", "value": "updated"}
]
}
}
```
That re-roots to `fixtures/svc/configs/app.yaml` (not a same-named file at the workspace root).
## Canonical parameter names
Use these names in plans, MCP args, and CLI flags (do not invent alternates):
| Concept | Canonical name | Notes |
|---------|----------------|-------|
| Text/identifier before | `old` | CLI: `--old`. Plans/MCP: `"old"`. |
| Text/identifier after | `new` | CLI: `--new`. Plans/MCP: `"new"`. |
| Doc path into a document | `selector` | CLI positional. Plans/MCP: `"selector"`. |
| AST rename / replace | path first | `ast rename PATH --old X --new Y`; plan `ast.rename` uses `path`/`old`/`new`. |
| Schema capability filter | `weak` / `medium` / `strong` | `schema --tier` only accepts these (not `small`/`large`). |
Some plan/MCP fields still **accept** legacy aliases (`from`/`to` for replace, `key` for doc selector) so older agent prompts keep working, but examples and new plans must use the canonical names above.
## Batching (the main speed win)
Six file edits via native tools = six round-trips. One `batch` call = one round-trip:
```bash
patchloom batch --apply <<'EOF'
doc.set config.json version "2.0.0"
doc.set config.yaml app.version "2.0.0"
replace README.md "1.0.0" "2.0.0"
file.create hello.txt "Hello, World!"
file.rename old.txt new.txt
md.upsert_bullet CHANGELOG.md "## Changes" "- Bumped to 2.0.0"
EOF
```
One line per operation. Double-quote values with spaces. Escapes in quotes: only `\"` and `\\` (literal `\n` is not a newline; use `tx`/MCP JSON for multi-line content).
On Windows (where heredocs are not available), write operations to a file and pass it:
```bash
patchloom batch ops.txt --apply
```
**Note:** Values are parsed as JSON first. An unquoted `1.0` is parsed as a number. To force a string, wrap in JSON quotes. Unix-like shells (bash/zsh): `doc.set config.json version '"1.0"'`. Windows (`cmd.exe`/PowerShell): `doc.set config.json version "\"1.0\""`.
For complex plans needing format/validate lifecycle, regex replace, or `--nth`, use `tx` with JSON:
```bash
patchloom tx plan.json --apply
```
Relative paths for batch ops files, `tx`/`explain` plan files, `patch` files, and `--files-from` lists resolve under `--cwd` (same base as operation targets). Absolute meta-input paths are unchanged. With `--contain`, those meta-input paths (and operation targets) must resolve inside the workspace: absolute paths under `--cwd` are allowed; `../` escapes and absolute paths outside the workspace are rejected.
In plan JSON, doc ops use the field name `selector` (not `key`). `key` is accepted as an alias if a model emits it, but prefer `selector`.
## Structured edits
```bash
# Edit a value in JSON/YAML/TOML by selector path (parser-backed, preserves comments)
patchloom doc set config.json version '"2.0.0"' --apply
patchloom doc merge config.yaml --value '{"db":{"pool":10}}' --apply
# Append a row to a markdown table
patchloom md table-append README.md --heading "## API" --row "| new | row |" --apply
```
On Windows, use double-quote escaping:
```bash
patchloom doc set config.json version "\"2.0.0\"" --apply
patchloom doc merge config.yaml --value "{\"db\":{\"pool\":10}}" --apply
patchloom md table-append README.md --heading "## API" --row "| new | row |" --apply
```
Add `--apply` to all write commands. Without it, patchloom previews changes without writing.
## Project configuration
Create `.patchloom.toml` in the project root to set defaults for all commands:
```toml
[write_policy]
ensure_final_newline = true
normalize_eol = "lf"
trim_trailing_whitespace = true
collapse_blanks = true
[tx]
strict = false
[exclude]
globs = ["target/**", "node_modules/**"]
```
CLI flags override config values. The file is searched upward from the working directory.
## Workflow examples
### Rename a function across a codebase
```bash
# Prefer AST-aware rename for code identifiers (skips strings/comments)
patchloom ast rename src/ --old old_function_name --new new_function_name --apply
# Fallback: text-based workflow when AST mode is unavailable
patchloom search --count "old_function_name" src/
patchloom replace "old_function_name" --new "new_function_name" src/ --apply
```
### Delete lines matching a pattern
```bash
# Delete entire lines containing a pattern; collapse consecutive blanks
patchloom replace 'dbg!' --whole-line --new '' src/ --collapse-blanks --apply
# Restrict to a line range (e.g. implementation only, skip tests)
patchloom replace 'TODO' --whole-line --range 10:200 --new '' notes.md --apply
```
### Edit a CI workflow
```bash
# Set a value in a YAML workflow by selector path (preserves comments and formatting)
patchloom doc set .github/workflows/ci.yml jobs.test.timeout-minutes 30 --apply
```
### Stale patch recovery via three-way merge
```bash
# Apply a patch using three-way merge when context has drifted
patchloom patch apply changes.patch --on-stale merge --apply
# Or use the merge subcommand directly
patchloom patch merge changes.patch --apply
# If merge produces conflicts, allow them to be written as markers
# WARNING: never commit files containing conflict markers
patchloom patch merge changes.patch --apply --allow-conflicts
# Diff from stdin (--stdin or a bare '-' path both work)
cat changes.patch | patchloom patch apply --stdin --apply
cat changes.patch | patchloom patch apply - --apply
```
```bash
# Same via transaction plan (JSON):
patchloom tx - --apply <<'EOF'
{"version": 1, "operations": [
{"op": "patch.apply", "diff": "...", "on_stale": "merge", "allow_conflicts": true}
]}
EOF
```
### Bump a version across config files
```bash
patchloom batch --apply <<'EOF'
doc.set package.json version "2.0.0"
doc.set Cargo.toml package.version "2.0.0"
replace README.md "1.0.0" "2.0.0"
md.upsert_bullet CHANGELOG.md "## [2.0.0]" "- Initial 2.0 release"
EOF
```
### Multi-file refactoring with a transaction
```bash
patchloom tx - --apply <<'EOF'
{"version": 1, "operations": [
{"op": "replace", "path": "src/config.rs", "old": "old_default", "new": "new_default"},
{"op": "doc.set", "path": "config.toml", "selector": "default_value", "value": "new_default"},
{"op": "md.replace_section", "path": "docs/config.md", "heading": "## Defaults",
"content": "The default value is now `new_default`.\n"}
]}
EOF
```
All operations succeed atomically or roll back together.
## AST-aware operations
Tree-sitter-backed operations that understand code structure (20 languages).
```bash
# Rename an identifier across a file, skipping strings and comments
patchloom ast rename src/lib.rs --old OldName --new NewName --apply
# Replace text only within a specific function body
patchloom ast replace src/config.rs --symbol default_timeout --old 30 --new 60 --apply
# List all symbol definitions
patchloom ast list src/lib.rs
# Find all references to a symbol
patchloom ast refs src/ --name my_function
```
AST rename, replace, and rewrite_signature can also be used in batch and tx plans:
```bash
# In a batch file (path, then old, then new — same names as CLI flags):
ast.rename src/lib.rs OldStruct NewStruct
ast.replace src/config.rs default_timeout "30" "60"
# path old parameters [return_type]:
ast.rewrite_signature src/lib.rs process "(x: u64)" "-> u64"
```
## Selector path syntax
All `doc` operations use selector paths to address values inside JSON, YAML, and TOML files.
| Syntax | Meaning | Example |
|--------|---------|---------|
| `name` | Object key | `database.host` |
| `[N]` | Array index (zero-based) | `servers[0].port` |
| `[*]` | Wildcard (all array elements) | `jobs[*].timeout` |
| `[key=val]` | Predicate (filter by field value) | `deps[name=express].version` |
Segments are separated by `.` or adjacent brackets. Examples:
```text
scripts.test # simple selector path
jobs[0].steps[*].name # index + wildcard
dependencies[name=react].version # predicate filter
```
## Exit codes
| Code | Meaning |
|------|---------|
| 0 | Success (operation completed, or no changes needed) |
| 1 | Failure (error during execution), or tx `rollback_failed` when mid-commit rollback could not fully restore files |
| 2 | Changes detected (`--check` mode found pending changes) |
| 3 | No matches (search/replace pattern miss, or tx/plan AST/md/doc target not found; `error_kind: no_matches`) |
| 4 | Parse error (malformed input file or plan) |
| 5 | Ambiguous (replacement matched multiple locations without `--nth`, or stale/missing patch context) |
| 6 | Validation failed (tx plan validation step returned non-zero; writes may remain when not strict) |
| 7 | Rollback (tx mid-commit failure or strict lifecycle failure; changes were rolled back) |
| 8 | Patch merge conflicts (`patch merge` or `--on-stale merge` without `--allow-conflicts`) |
| 9 | Tx operation staging failure (`operation_failed`) |
**Doc write JSON tip:** With `--json` (CLI) or MCP write tools / `execute_plan`, doc delete success includes `changed` (bool) and `removed` (usize). Multi-op plans also list per-op rows under `mutations`. Exit 0 / `ok: true` with `removed: 0` means idempotent cleanup (nothing matched); do not assume data was deleted from exit status alone.
## Operations (from schema registry)
- `replace`: Replace text in a file using literal string matching.
- `file.append`: Append content to an existing file.
- `file.prepend`: Prepend content to an existing file.
- `file.create`: Create a new file with specified content.
- `file.delete`: Delete a file.
- `file.rename`: Rename (move) a file.
- `tidy.fix`: Normalize whitespace, line endings, and final newline in a file.
- `doc.set`: Set a value at a selector path in a JSON, YAML, or TOML file. Parser-backed; output is always valid.
- `doc.delete`: Delete a value at a selector path in a JSON, YAML, or TOML file. CLI --json and MCP/tx success include changed and removed (0 on missing key; exit 0 / ok is idempotent).
- `doc.merge`: Deep-merge a JSON object into the root of a document.
- `doc.append`: Append a value to an array at a selector path.
- `doc.move`: Move a value from one selector path to another within the same file.
- `doc.ensure`: Set a value only if the selector path does not already exist.
- `md.replace_section`: Replace the body of a markdown section identified by heading.
- `md.upsert_bullet`: Insert or update a bullet point under a markdown heading.
- `md.table_append`: Append a row to a markdown table under a heading.
- `md.insert_after_heading`: Insert content immediately after a markdown heading.
- `md.insert_before_heading`: Insert content immediately before a markdown heading.
- `md.move_section`: Move a heading section to a new position (same-file reorder or cross-file move). Exactly one of before or after is required.
- `patch.apply`: Apply a unified diff patch to one or more files. Supports three-way merge on stale context.
- `doc.prepend`: Prepend a value to the beginning of an array at a selector path.
- `doc.update`: Set a new value at every location matching a selector. Use wildcards (items[*].enabled) or selector predicates (items[name=foo].v). Not a separate --where flag; the filter is part of the selector string.
- `doc.delete_where`: Delete array elements matching a key=value predicate via --predicate (CLI) or the predicate field (plans). For scalar arrays use .=x, _=x, or value=x. Different from doc.update, which filters inside the selector path. CLI --json and MCP/tx success include changed and removed (0 when no elements match; exit 0 / ok is idempotent).
- `search`: Search for text across files with optional regex, context, and count assertion. Supports advanced layered ignores: literal (vs regex), globs (include), exclude_patterns, custom_ignore_filenames, max_results, before_context/after_context.
- `read`: Read file contents with optional line range.
- `md.dedupe_headings`: Remove duplicate markdown headings in a file.
- `md.lint_agents`: Lint an AGENTS.md file for common issues.
- `ast.rename`: AST-aware rename: rename identifiers skipping strings and comments.
- `ast.replace`: Replace text within a specific symbol's body (AST-scoped).
- `ast.rewrite_signature`: Rewrite a function signature with structured fields (visibility, parameters, return_type) or a full new_signature string. Multi-language via tree-sitter.
- `ast.insert`: Insert code at a structurally-aware position: inside a container, or after/before a named symbol.
- `ast.wrap`: Wrap existing code in a structural block (module, impl, cfg, etc.).
- `ast.imports`: Manage import/use statements: add (idempotent), remove, deduplicate.
- `ast.reorder`: Reorder symbols within a file or scope by name, kind, or custom order.
- `ast.group`: Group symbols into a named module within a file.
- `ast.move`: Move symbols between files with optional target creation.
- `ast.extract_to_file`: Extract a symbol to a separate file with optional module unwrapping.
- `ast.split`: Split a file into multiple target files by distributing symbols.
## Troubleshooting
If a command produces unexpected results, enable verbose logging to see what patchloom is doing internally:
```bash
patchloom --verbose <command> [args]
# or via environment variable:
PATCHLOOM_LOG=1 patchloom <command> [args]
```
Diagnostic messages are printed to stderr prefixed with `[patchloom]`.