Metadata-Version: 2.4
Name: hash-edit
Version: 0.1.0
Summary: Line-hash anchored read/edit/write for AI coding agents — windowed reads, strict version checks, atomic writes
Keywords: llm,ai,agent,file-editing,hashline
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.11
Project-URL: Homepage, https://github.com/jansiml/hash-edit
Project-URL: Repository, https://github.com/jansiml/hash-edit
Description-Content-Type: text/markdown

# hash-edit (Python)

Line-hash anchored read/edit/write for AI coding agents — windowed reads, strict version checks, atomic writes.

## Install

```bash
pip install hash-edit
# or
uv add hash-edit
```

## Quick start

```python
from hash_edit import HashEditHarness

h = HashEditHarness("/path/to/project")

# 1. Read — returns rendered lines with hashes and a full-file version digest
result = h.read("src/app.py")
# result["version"]  == "4a3f..."
# result["lines"]    == ["1:ab|import os", "2:cd|", "3:ef|def main(): ..."]
# result["total_lines"] == 3

# 2. Edit — anchored ops verified against expected_version
h.edit(
    "src/app.py",
    [
        {"op": "replace", "line": 3, "hash": "ef", "lines": ["def main():", "    pass"]},
    ],
    expected_version=result["version"],
)

# 3. Write — safely create or overwrite; expected_version required when overwriting
h.write("src/new_file.py", "# new content\n")
```

## Windowed reads

For large files, pass `start_line` / `end_line` to read only the lines you need (saves 55–87% tokens):

```python
result = h.read("src/app.py", start_line=100, end_line=150)
# result["lines"]       — 51 rendered lines, numbered 100–150
# result["total_lines"] — full file length (e.g. 1 200)
# result["version"]     — full-file digest, same as a whole-file read
```

The `version` is always the full-file blake2s digest, so a windowed read still gives you a valid `expected_version` to pass to `edit()`.

## Error classes

| Error | When it is raised |
|---|---|
| `VersionConflictError` | The file changed between your last `read()` and the current `edit()` or `write()` |
| `AnchorMismatchError` | A `hash` field does not match the current line; the error message includes updated rendered context so the agent can retry immediately |
| `InvalidOperationError` | The edit payload is structurally invalid (missing field, out-of-range line, no-op replace, etc.) |
| `PathEscapeError` | The requested path resolves outside the configured root directory |
| `MixedNewlineError` | The file mixes newline styles (`\n`, `\r\n`, `\r`) and is rejected before mutation |
| `FileEncodingError` | The file cannot be decoded with the configured encoding (UTF-8 by default) |

All six inherit from `HashEditError`.

## Further reading

- [Root README](https://github.com/jansiml/hash-edit#readme) — project overview, TypeScript package, and benchmarks
- [`docs/spec.md`](https://github.com/jansiml/hash-edit/blob/main/docs/spec.md) — full protocol specification
- [`docs/INTERFACE_DESIGN.md`](https://github.com/jansiml/hash-edit/blob/main/docs/INTERFACE_DESIGN.md) — design rationale
