<a href="https://pi.dev/packages/edit-o-matic">
  <img src="https://img.shields.io/badge/pi-package-6f42c1" alt="Pi Package">
</a>
<a href="https://www.npmjs.com/package/edit-o-matic">
  <img src="https://img.shields.io/npm/v/edit-o-matic" alt="npm version">
</a>
<a href="https://github.com/bighornwoods/edit-o-matic">
  <img src="https://img.shields.io/github/stars/bighornwoods/edit-o-matic?style=flat&color=yellow" alt="GitHub stars">
</a>
<a href="https://github.com/bighornwoods/edit-o-matic/blob/main/LICENSE">
  <img src="https://img.shields.io/npm/l/edit-o-matic?color=blue" alt="License">
</a>

# editomatic

**Whitespace-tolerant edit fallback for Pi — for local/quantized LLM users who also use [gentle-pi](https://pi.dev/packages/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](./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 again
```

`editomatic` 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:

1. Saves previous content to `yourfile.ext.editomatic-bak`
2. Writes new content to a temp file, then renames (atomic on the same filesystem)
3. For Python paths, runs best-effort `python -m py_compile` **before** overwrite when an interpreter is available (errors cite the real path and proposed-file line numbers, not a temp path)

### Dry-run preview

```json
{ "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

```bash
export EDITOMATIC_STRICT=1   # exact + fuzzy only
```

## Install

```bash
pi install npm:edit-o-matic
```

Project-local:

```bash
pi install npm:edit-o-matic -l
```

Confirm **1.1.0 or later**. See [CHANGELOG.md](./CHANGELOG.md).

### Recovery from backup

```bash
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

```typescript
{
  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 `oldText` small 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 `newText` deletes 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](./CHANGELOG.md)**.

## Building

```bash
npm install
npm run build
npm run check
npm test
```

## License

MIT — [LICENSE](LICENSE)
