$npx -y skills add humorless/clj-native-agent --skill clj-replaceUse when replacing Clojure or Babashka code but str_replace fails due to formatting differences - compares S-expression structure instead
| 1 | # clj-replace: Structural Clojure Code Replacement |
| 2 | |
| 3 | A Clojure-aware code replacement tool that improves on Claude Code's built-in `str_replace` by comparing code structure (S-expressions) rather than literal text. |
| 4 | |
| 5 | ## When to Use |
| 6 | |
| 7 | Use `/clj-replace` when: |
| 8 | |
| 9 | - Claude Code's `str_replace` fails because formatting (whitespace, indentation, line breaks) differs between the `old_string` and target code |
| 10 | - You need to replace code but the exact whitespace/formatting doesn't match the source |
| 11 | - You want replacements to preserve the original file's formatting and style |
| 12 | |
| 13 | **Example:** If `str_replace` fails with "old_string not found" but the code structure looks identical, switch to `clj-replace`. |
| 14 | |
| 15 | ## How It Works |
| 16 | |
| 17 | Instead of comparing text character-by-character, `clj-replace` uses structural comparison: |
| 18 | |
| 19 | 1. Parses both the `old-string` and file contents into S-expression trees (zippers) |
| 20 | 2. Walks the file and compares each node's structure (`=` on sexpr) with the target |
| 21 | 3. Formatting differences (spaces, indentation, newlines) are **ignored** during comparison |
| 22 | 4. When a match is found, replaces the node while **preserving** the original file's formatting |
| 23 | |
| 24 | This means these are all equivalent: |
| 25 | ```clojure |
| 26 | (foo bar) ≡ (foo bar) ≡ (foo |
| 27 | bar) |
| 28 | ``` |
| 29 | |
| 30 | ## Parameters |
| 31 | |
| 32 | **Positional Arguments:** |
| 33 | |
| 34 | 1. `filename` — Path to the Clojure source file to modify |
| 35 | 2. `old-string` — Code snippet to find (as a string) |
| 36 | 3. `new-string` — Code snippet to replace it with (as a string) |
| 37 | |
| 38 | ## Usage |
| 39 | |
| 40 | ``` |
| 41 | /clj-replace path/to/file.clj "old code here" "new code here" |
| 42 | ``` |
| 43 | |
| 44 | ## Examples |
| 45 | |
| 46 | ### Example 1: Whitespace Variation |
| 47 | |
| 48 | **File content:** |
| 49 | ```clojure |
| 50 | (defn update-user |
| 51 | [db id attrs] |
| 52 | (merge-user db id attrs)) |
| 53 | ``` |
| 54 | |
| 55 | **Command:** |
| 56 | ``` |
| 57 | /clj-replace src/app.clj "(merge-user db id attrs)" "(update-user-in-db db id attrs)" |
| 58 | ``` |
| 59 | |
| 60 | Works even though file has different indentation. Replaces the exact form while preserving file's style. |
| 61 | |
| 62 | ### Example 2: Multi-line Form |
| 63 | |
| 64 | **File content:** |
| 65 | ```clojure |
| 66 | (some-function |
| 67 | arg1 |
| 68 | arg2 |
| 69 | arg3) |
| 70 | ``` |
| 71 | |
| 72 | **Command:** |
| 73 | ``` |
| 74 | /clj-replace src/app.clj "(some-function arg1 arg2 arg3)" "(other-function arg1 arg2 arg3)" |
| 75 | ``` |
| 76 | |
| 77 | Matches regardless of how the original is formatted across lines. |
| 78 | |
| 79 | ## Exit Codes |
| 80 | |
| 81 | - `0` — Success: one or more nodes replaced |
| 82 | - `1` — Error: file doesn't exist, or `old-string`/`new-string` are invalid Clojure |
| 83 | - `2` — Error: no matching S-expression found in the file |
| 84 | - `3` — Error: multiple matching S-expressions found (ambiguous); user must provide more specific `old-string` |
| 85 | |
| 86 | ## Output |
| 87 | |
| 88 | On success: |
| 89 | ``` |
| 90 | ✓ Replaced 1 node(s) in path/to/file.clj |
| 91 | Location: line 42, column 5 |
| 92 | Old: (merge-user db id attrs) |
| 93 | New: (update-user-in-db db id attrs) |
| 94 | ``` |
| 95 | |
| 96 | If multiple matches: |
| 97 | ``` |
| 98 | ✗ Found 3 matching S-expressions in path/to/file.clj (ambiguous) |
| 99 | 1. line 42, column 5: (foo bar) |
| 100 | 2. line 85, column 10: (foo bar) |
| 101 | 3. line 120, column 1: (foo bar) |
| 102 | Please provide a more specific old-string (e.g., include surrounding context) |
| 103 | ``` |
| 104 | |
| 105 | ## Implementation Details |
| 106 | |
| 107 | Uses `rewrite-clj` library for: |
| 108 | - Format-preserving parsing and modification |
| 109 | - Structural comparison of S-expressions |
| 110 | - Safe node replacement that maintains whitespace and style |
| 111 | |
| 112 | The tool respects Clojure's semantics: two expressions are equivalent if they parse to the same structure, regardless of superficial formatting. |