# udiffx
Parse and apply an AI-optimized “file changes” envelope that carries multiple file operations in a single block, using unified diff patches for updates.
This crate is designed for LLM output that needs to be machine-parsable and efficient for large files with small edits.
[Doc for LLM](doc/for-llm/api-reference-for-llm.md)
## Concept, `FILE_CHANGES`
A response contains one root container:
- `<FILE_CHANGES> ... </FILE_CHANGES>`
Inside it, you can mix multiple directives:
- `<FILE_NEW file_path="..."> ... </FILE_NEW>`
- `<FILE_PATCH file_path="..."> ... </FILE_PATCH>` (unified diff content)
- `<FILE_RENAME from_path="..." to_path="..." />`
- `<FILE_DELETE file_path="..." />`
Notes:
- Tags are XML-like but not intended to be strictly XML-compliant.
- The parser is tag-based. It extracts only the above tags, and content does not need XML escaping.
- Self-closing tags like `<FILE_DELETE ... />` are supported.
## API overview
The crate exposes two main operations:
- Extract: parse the first `<FILE_CHANGES>` block from a string.
- Apply: execute the extracted directives against a base directory.
Key public types:
- `FileChanges`, an iterable list of directives.
- `FileDirective`, one directive (new, patch, rename, delete, fail).
- `ApplyChangesStatus`, per-directive success and error reporting.
- `Error` / `Result<T>`, the crate error type and alias.
## Gathering file context
Use `load_files_context` to gather files matching globs and format them for an LLM context.
```rust
use udiffx::{load_files_context, Result};
fn main() -> Result<()> {
let base_dir = "./my-project";
let globs = &["src/**/*.rs", "Cargo.toml"];
if let Some(context) = load_files_context(base_dir, globs)? {
println!("{context}");
}
Ok(())
}
```
Output format:
```xml
<FILE_CONTENT path="Cargo.toml">
... content ...
</FILE_CONTENT>
<FILE_CONTENT path="src/main.rs">
... content ...
</FILE_CONTENT>
```
## Extracting changes from text
Use `extract_file_changes` to parse a model response or any input string.
```rust
use udiffx::{extract_file_changes, Result};
fn main() -> Result<()> {
let input = r#"
Some text...
<FILE_CHANGES>
<FILE_NEW file_path="src/hello.rs">
pub fn hello() { println!("Hello"); }
</FILE_NEW>
<FILE_DELETE file_path="old.txt" />
</FILE_CHANGES>
"#;
let (changes, _extruded) = extract_file_changes(input, false)?;
if changes.is_empty() {
println!("No changes found");
return Ok(());
}
for d in &changes {
println!("{d:?}");
}
Ok(())
}
```
`extract_content` parameter:
- `extract_content = false` parses tags and returns `extruded = None`.
- `extract_content = true` also returns the input with the extracted `<FILE_CHANGES>` block removed as `Some(String)`.
## Applying changes to disk
Use `apply_file_changes` to execute directives relative to a base directory.
- All file paths are treated as relative to `base_dir`.
- The crate performs basic path safety checks to ensure operations stay within `base_dir`.
- Patch application uses `diffy` (unified diff parsing and application).
```rust
use simple_fs::SPath;
use udiffx::{apply_file_changes, extract_file_changes, Result};
fn main() -> Result<()> {
let base_dir = SPath::new("./my-project");
let input = r#"
<FILE_CHANGES>
<FILE_PATCH file_path="src/main.rs">
@@ -1,3 +1,3 @@
-fn main() { println!("Hello"); }
+fn main() { println!("Hello, world"); }
</FILE_PATCH>
</FILE_CHANGES>
"#;
let (changes, _) = extract_file_changes(input, false)?;
let status = apply_file_changes(&base_dir, changes)?;
for d in status.items {
if d.success() {
println!("OK {} {}", d.kind(), d.file_path());
} else {
println!(
"FAIL {} {}: {}",
d.kind(),
d.file_path(),
d.error_msg().unwrap_or("unknown error")
);
}
}
Ok(())
}
```
## Directive behavior
- `FILE_NEW`: creates or overwrites a file. Parent directories are created.
- `FILE_PATCH`: reads the target file, applies a unified diff, and writes the result back.
- `FILE_RENAME`: renames or moves `from_path` to `to_path`.
- `FILE_DELETE`: removes a file or directory recursively.
If extraction fails for a directive (unknown tag, missing attribute, etc.), the directive is represented as:
- `FileDirective::Fail { kind, file_path, error_msg }`
When applying, `Fail` directives always yield an error for that directive and are reported via `ApplyChangesInfo`.
## Format tips for LLM output
- Always emit exactly one `<FILE_CHANGES>` block when you intend to apply changes.
- Prefer `FILE_PATCH` for small edits to large files.
- Use self-closing tags for rename and delete when convenient:
- `<FILE_RENAME from_path="a" to_path="b" />`
- `<FILE_DELETE file_path="path" />`
## System prompt (optional)
The crate includes the recommended system instructions for LLMs to ensure they output the correct format. This is available via the `prompt` feature.
```toml
[dependencies]
udiffx = { version = "0.1", features = ["prompt"] }
```
```rust
use udiffx::prompt;
let instructions = prompt();
// Pass this to your LLM system message.
```
## License
MIT OR Apache-2.0