# Patchloom 0.9.0
This release closes parity gaps across all three channels (CLI, MCP, library API) and fixes a security-relevant path traversal bypass. 3 new features, 6 bug fixes, and 54 new tests across 19 commits.
## Highlights
Fuzzy edit matching, context-anchored replacements, and the prepend operation each had one channel missing. This release fills all the gaps: fuzzy fallback reaches the library API, context anchoring reaches both the CLI and library API, and prepend reaches the CLI. Every replacement feature is now available in all three channels. A path traversal bypass in the containment module was also identified and patched, hardening the security boundary for all channels.
## New features
- **Fuzzy fallback for `replace_in_content` (library API parity).** The in-memory replace API now accepts `fuzzy: true` in `ReplaceOptions`. When exact match fails, patchloom automatically tries Jaro-Winkler similarity matching, then returns suggestions if fuzzy also fails. This eliminates ~15 lines of manual fallback glue that library embedders previously needed. The tx engine (CLI, MCP, and plans) already had this capability. (#1292)
- **`before_context` / `after_context` for replace (CLI and library API parity).** Anchor text that disambiguates which match to target when a pattern appears multiple times in a file. The MCP `replace_text` tool and tx plans already supported these fields; this release adds CLI flags (`--before-context`, `--after-context`) and exposes them in the library API's `ReplaceOptions`. Supports fuzzy anchor matching as a fallback. (#1290, #1310)
- **`prepend` command (CLI parity).** New CLI command that prepends content to the beginning of an existing file. The MCP `prepend_file` tool and library `file_prepend` API already existed; this completes the CLI surface. Accepts `--content` or `--stdin` (mutually exclusive). (#1290)
## Bug fixes
- **Path traversal bypass in containment.** `canonicalize_or_ancestor` silently dropped `..` components from non-existent path segments, allowing paths like `workspace/nonexistent/../../outside/secret.txt` to escape the workspace boundary. Fixed by adding lexical normalization of `.` and `..` before the ancestor walk. (#1296)
- **Markdown `replace-section` duplicated headings.** When replacement content started with the same heading being replaced, the heading appeared twice in the output. Now automatically stripped. (#1306)
- **Markdown `replace-section` dropped blank lines.** Replacing a section's content removed the trailing blank line separator before the next heading, producing tightly packed markdown. Now preserves the original spacing. (#1307)
- **YAML `doc set` stripped quote styles.** Setting a value on a double-quoted or single-quoted YAML scalar lost the quotes, producing a plain scalar. Now uses in-place value replacement that preserves the original quoting style. (#1283)
- **Per-extension formatters were dead code.** The `[format.by_extension]` table in `.patchloom.toml` was parsed and validated but never wired through to the format runner. Now correctly discovers modified files and applies extension-specific formatters. (#1285)
- **Patch, doc, and replace exit code fixes.** Creation patches no longer falsely report the target as missing; `doc move` rejects moving a key to its own descendant; replace preview mode returns consistent exit codes. (#1297)
## Library API changes
- **Bline issues #1287, #1288, #1289.** `ast.rename` in tx plans now supports directory paths (recursive rename across all source files); numeric dot-notation selectors (e.g., `env.0.value`) resolve as array indices; operation field descriptions are now included in the `execute_plan` JSON schema for better agent tooling. (#1291)
- **`selector` replaces `key`.** The `key` field on doc operations has been renamed to `selector` for consistency with selector-based commands. Old plans using `key` continue to work via serde aliases. (#1293)
## Internal improvements
- Deduplicated append/prepend implementation via shared `ContentPosition` enum. (#1299)
- Removed all 45 `#[non_exhaustive]` attributes and tightened `pub` to `pub(crate)` on `write.rs` internals. (#1300)
- Extracted the agent-rules generator from the oversized `cmd/mod.rs` into its own module. (#1301)
- Removed backward-compatibility serde aliases and legacy error prefixes. (#1298)
- Unified divergent `parse_line_range` implementations. (#1304)
## Numbers
| Unit tests | 1,983 | 2,019 | +36 |
| Integration tests | 838 | 856 | +18 |
| PTY tests | 10 | 10 | -- |
| **Total tests** | **2,831** | **2,885** | **+54** |
| CLI commands | 22 | 23 | +1 |
| MCP tools | 54 | 54 | -- |
| Commits | -- | 19 | -- |