edit-o-matic
v1.1.0
Published
Whitespace-tolerant edit fallback for Pi — fail-closed relative-indent matching, match-failure diagnostics, dry-run preview, correct indent-unit detection, Myers line diffs, multi-edit hardening, backups, atomic writes, and remapped Python syntax errors.
Maintainers
Readme
editomatic
Whitespace-tolerant edit fallback for Pi — for local/quantized LLM users who also use gentle-pi.
Upgrade to 1.1.0+ (diagnostics + dry-run)
Versions before 1.0.5 could silently corrupt indent-sensitive files (especially Python) when aggressive fallback matching flattened indentation and rewrote the wrong region.
1.0.5+ fail-closed matching · 1.0.6+ backups + atomic write · 1.0.7 multi-edit hardening · 1.0.8 Myers line diffs · 1.1.0 match-failure diagnostics, dry-run preview, correct indent-unit detection, remapped Python syntax errors.
Full details: CHANGELOG.md · lockdown:
EDITOMATIC_STRICT=1
editomatic is not a replacement for Pi's built-in edit tool. It is a fallback
for models that struggle with exact text matching due to indentation drift,
trailing whitespace, or Unicode normalization differences — common failure modes
of local and quantized language models.
The problem
Pi's built-in edit tool uses exact string matching. When a model's output
contains different indentation levels, trailing spaces, or Unicode smart quotes,
the edit fails. Quantized models (4-bit, 8-bit) are especially prone to these
drift patterns.
# What the model sends:
{ "oldText": "function hello() {}" }
# What the file actually has:
" function hello() {\n // does nothing\n }"
# Result: edit fails, model has to guess againeditomatic handles this with progressive fallback matching and fail-closed
safety so it never silently destroys structure.
How it works
Matching strategies (first success wins):
| Strategy | What it does | Speed |
|---|---|---|
| Exact | Byte-perfect indexOf match | Fastest |
| Fuzzy | Strips trailing whitespace, normalizes Unicode; always replaces at line level | Fast |
| Relative-indent | Same content after fuzzy normalization, with matching relative indentation between lines (absolute indent may differ) | Medium |
Pure “strip all leading whitespace and hope” is not used. Two blocks that only look identical after flattening structure are rejected or treated as ambiguous.
Replacement preserves:
- Original BOM and line endings (CRLF vs LF)
- Unchanged lines outside the match
- Tab/space convention and relative indent structure
- Trailing newlines when intentional in
newText
Write safety
Every successful write:
- Saves previous content to
yourfile.ext.editomatic-bak - Writes new content to a temp file, then renames (atomic on the same filesystem)
- For Python paths, runs best-effort
python -m py_compilebefore overwrite when an interpreter is available (errors cite the real path and proposed-file line numbers, not a temp path)
Dry-run preview
{ "path": "app.py", "dryRun": true, "edits": [{ "oldText": "...", "newText": "..." }] }Matches and builds the same Myers diff (and Python syntax gate) but does not write
or create a backup. Use when verifying a large newText before committing the change.
Match-failure diagnostics
When oldText is not found, the error includes the closest candidate region, the
first differing line (expected vs actual), and whether the drift is whitespace-only.
That saves a full re-read when a successive edit used stale text.
Emergency lockdown
export EDITOMATIC_STRICT=1 # exact + fuzzy onlyInstall
pi install npm:edit-o-maticProject-local:
pi install npm:edit-o-matic -lConfirm 1.1.0 or later. See CHANGELOG.md.
Recovery from backup
cp path/to/file.py.editomatic-bak path/to/file.py| Variable | Effect |
|----------|--------|
| EDITOMATIC_STRICT=1 | Exact + fuzzy matching only |
| EDITOMATIC_NO_BACKUP=1 | Skip sidecar backup |
| EDITOMATIC_SKIP_PYTHON_CHECK=1 | Skip py_compile for .py files |
Usage
The tool registers as editomatic in Pi's tool registry.
Parameters
{
path: string; // Path to file (relative or absolute)
edits: Array<{
oldText: string; // Text to find (whitespace-tolerant)
newText: string; // Replacement text ("" = delete)
}>;
dryRun?: boolean; // Preview diff + syntax check; no write
}Tips
- Keep
oldTextsmall and unique — ambiguous matches are rejected; not-found errors include first-diff diagnostics - One call, multiple edits — searched against original content; applied bottom-up safely
- After a successful edit, re-read or use updated text for further edits to the same region
- Preserve relative indentation in multi-line blocks (critical for Python)
dryRun: true— verify large replacements before writing- Deletion — empty
newTextdeletes matched lines;"\n"inserts a blank line
When to use editomatic vs built-in edit
| Situation | Use |
|---|---|
| Standard edit on well-formatted files | Built-in edit |
| Models that produce indentation drift | editomatic |
| Mixed tab/space files | editomatic |
| Unicode drift (smart quotes, special dashes) | editomatic |
| Quantized or local models | editomatic |
Indentation context injection
On before_agent_start, editomatic injects per-file indent style, short indent
examples, and a Python-specific reminder that indentation is syntax. That reduces
how often fallback matching is needed.
Package contents
| Path | Purpose |
|---|---|
| index.ts | Extension entry point |
| smart-edit.ts | Matching engine + reindent |
| write-safety.ts | Backup, atomic write, Python syntax gate |
| test/ | Unit tests (matching, corruption regressions, write-safety) |
| CHANGELOG.md | Version history and integrity notices |
Changelog
See CHANGELOG.md.
Building
npm install
npm run build
npm run check
npm testLicense
MIT — LICENSE
