SafeEdit is a Windows-friendly Rust CLI for applying complex text/code edits while guaranteeing preview-before-write semantics, deterministic targeting, and reversible, atomic changes. It’s designed as a safer, more powerful alternative to manual apply_patch workflows—especially for large refactors, multi-file replacements, or encoding cleanup jobs.
- Preview-first: Every edit renders a diff before touching disk. You can skip, apply once, or “apply all”.
- Deterministic targeting: Literal, regex, and block operations include match guards (
--expect,--after-line, etc.) plus helpful suggestions when nothing matches. - Encoding aware: Detects BOMs/encodings, preserves original line endings, and can convert troublesome files to UTF-8.
- Atomic & reversible: Writes happen via temp files,
.bakrotation (file.bak,file.bak1, …), and optional undo patches (--undo-log), so every change is recoverable. - Traceable history: Each action logs JSON lines with timestamps, commands, files, and line spans for later review (
safeedit log,safeedit report). - Windows ergonomics: Works out-of-the-box on PowerShell, guards against runaway output (200-line diff pages, 5 MB cap, 64 KB/line), and skips suspected binary files unless you explicitly opt in.
| Command | Purpose | Example |
|---|---|---|
replace |
Literal/regex replacements with diff previews and match guards; accepts literal, stdin, clipboard, or heredoc (--with-here TAG) inputs. |
safeedit replace --target src --literal --pattern "foo" --with-here END + pasted text ending in END |
rename |
Case-aware identifier renames with word-boundary controls. | safeedit rename --target app --from VERSION --to APP_VERSION --word-boundary --case-aware |
block |
Insert/replace multi-line regions bounded by markers or single-line anchors via --insert-after/--insert-before, with heredoc (--body-here) inputs, marker-overlap detection (--allow-marker-overlap to override), and --expect-blocks guards. (--mode after/before act as aliases for insert.) |
safeedit block --target file.rs --insert-after "fn main()" --body-here BODY |
write |
Create or overwrite files with diff previews, backups, and explicit line-ending controls—perfect for staging snippets. | safeedit write --path snippets/helper.rs --body-here SNIP --line-ending crlf --apply |
apply |
Replay unified .patch/.diff files (modify/create/delete/rename) through the preview/approval pipeline while preserving original newline styles. |
safeedit apply --patch changes.diff --apply |
review |
Safe file viewing: --head, --tail, --lines, --search, --step, or long-running --follow. Built-in pager kicks in past ~200 diff lines. |
safeedit review --target app/main.rs --head 20 --search todo |
normalize |
Detect/repair zero-width chars, control chars, trailing spaces, final newlines, encoding mojibake, and convert encodings. | safeedit normalize --target docs --trim-trailing-space --ensure-eol --convert-encoding utf-8 --apply |
batch |
Execute YAML/JSON “recipes” that chain supported verbs (replace, block, rename, normalize) with shared review logging. |
safeedit batch qa_sandbox/recipes/refactor.yaml --apply --yes |
report |
Summaries of logged edits for CI/standups (table or json). |
safeedit report --since 2025-11-08T14:00:00-07:00 |
log |
Tail the rolling .safeedit/change_log.jsonl audit trail. |
safeedit log --tail 20 |
cleanup |
Find/remove .bak/.bakN safety files once you’re confident in edits. |
safeedit cleanup --root . --apply --yes |
Additional niceties:
--pager {auto,always,never}toggles the internal diff viewer (auto pages when diffs exceed ~200 lines but stay under the 5 MB/64 KB guardrails).--colorand--jsonadjust output style for automation.- Path auto-resolution walks up from the current directory to suggest likely files when a target can’t be found.
qa_sandbox/plusdocs/qa_testing_checklist.mddefine a repeatable regression suite covering review, replace, rename, block, apply, normalize, batch, report/log, and cleanup scenarios.
- Prerequisites
- Rust toolchain (1.75+ recommended) with Cargo.
- Windows 10/11 with PowerShell (Unix shells work too, but the guardrails are tuned for Windows consoles).
- Clone & build
The binary will be at
git clone <repo-url> safeedit cd safeedit\safeedit cargo build --release
safeedit\target\release\safeedit.exe. - (Optional) Add to PATH
$env:Path += ";$PWD\target\release"
# Preview first replacement (no files touched)
safeedit replace --target src/lib.rs --literal --pattern "hello" --with "hello SafeEdit"
# Apply the change after review
safeedit replace --target src/lib.rs --literal --pattern "hello" --with "hello SafeEdit" --apply
# Seed a snippet file for block insertions (with explicit CRLF endings)
safeedit write --path qa_sandbox/block_body.txt --body 'println!("generated block");' --body 'println!("extra");' --line-ending crlf --apply
# Or capture multi-line text interactively via heredoc
safeedit block --target qa_sandbox/app/main.rs --start-marker "// BEGIN GENERATED" --end-marker "// END GENERATED" --mode replace --body-here BLOCK
println!("generated block");
println!("extra");
BLOCK
# Anchor on a single line without an end marker and enforce a single hit
safeedit block --target qa_sandbox/app/main.rs --insert-after 'fn main() {' --body-here BODY --expect-blocks 1
println!("loggable step");
BODY
> PowerShell tip: wrap markers/start/end strings in single quotes (e.g., `'// BEGIN GENERATED'`) so embedded double quotes or parentheses aren't eaten by the shell.
> Block tip: if the replacement body still contains your start/end markers (e.g., identical headings), SafeEdit now aborts to prevent duplicate inserts. Adjust the sentinel (heading + next heading) or pass `--allow-marker-overlap` only when you truly need the markers to remain in the body.
# Rename identifiers across the project, preserving case
safeedit rename --target app --from VERSION --to APP_VERSION --word-boundary --case-aware --apply
# Apply a git-style patch (modify + create files)
safeedit apply --patch fix.diff --apply
# Normalize documentation: trim trailing spaces, ensure final newline, convert encoding
safeedit normalize --target docs --trim-trailing-space --ensure-eol --convert-encoding utf-8 --apply
# Run a batch recipe that chains replace + normalize steps
safeedit batch qa_sandbox/recipes/refactor.yaml --apply --yes
# Inspect recent edits
safeedit report --since 2025-11-08T15:00:00Z --format table
safeedit log --tail 10Reminder: Safeedit’s default dry-run preview is the review step—only add
--apply/--yes(auto-apply) after you’ve looked at the diff. Auto-approve skips the safety prompt, so use it sparingly.
- Diff previews everywhere with
apply/skipprompts and--yes/--auto-applyoverrides for CI. - Auto-apply caution:
--apply+--yes/--auto-applyskips the confirmation prompt; only use it once you’ve reviewed the diff. - Atomic writes via temp files + rename; backups rotate (
.bak,.bak1, …) unless--no-backupis used. - Undo artifacts:
--undo-log <dir>drops reverse patches you can replay withpatch -R. - Encoding fidelity: detection respects BOM > chardet > UTF-8 fallback; newline preservation ensures CRLF files remain CRLF even after patches.
- Guardrails: 200-line diff window, 5 MB total diff output ceiling, 64 KB per line, binary-file detection, and follow-mode safeguards.
- Logging & reporting: every command writes JSONL entries consumed by
safeedit report/safeedit log.
docs/safe_edit_tool_plan.md— vision, architecture, and roadmap (including planned commands such asscript).docs/qa_testing_checklist.md— a reproducible end-to-end validation script (environment prep, sandbox creation, command coverage, cleanup, usability notes).qa_sandbox/— sample workspace with nested folders, encoding edge cases, and batch recipes for testing.
- Diff UX polish (side-by-side viewer, HTML export).
- Review ergonomics (
--followenhancements, bookmark/search helpers). - Batch-runner verb expansion and optional scripted transforms (
scriptcommand). - Additional normalize/report output formats driven by user feedback.
Contributions and experiments are welcome—just follow the QA checklist and keep the safety guarantees front-and-center. Happy editing!