# Hashline Edit Tool

Edit files using anchor-based line references for precise, hash-validated edits.

## Description

The `hashline_edit` tool enables file editing using short content-hash anchors (`N:hhhh`) instead of requiring exact text reproduction. Each line's anchor includes a 4-character hash of its content. If the file has changed since the model last read it, the hash won't match and the edit is cleanly rejected.

Use this tool together with `read_file(hashline=True)` and `grep_search(hashline=True)`, which return anchors for each line.

## Use Cases

- Making targeted edits after reading a file with `read_file(hashline=True)`
- Replacing single lines, line ranges, or inserting new lines by anchor
- Batch editing multiple locations in a single atomic call
- Falling back to string replacement when anchors are not available

## Usage

```python
import json

# First, read the file with hashline mode to get anchors
content = read_file(path="app.py", hashline=True)
# Returns lines like: 1:a3b1|def main():  2:f1c2|    print("hello")  ...

# Then edit using the anchors
hashline_edit(
    path="app.py",
    edits=json.dumps([
        {"op": "set_line", "anchor": "2:f1c2", "content": '    print("goodbye")'}
    ])
)
```

## Operations

The `edits` parameter is a JSON array of operation objects. Each object must have an `"op"` field:

| Op | Fields | Behavior |
|---|---|---|
| `set_line` | `anchor`, `content` | Replace one line identified by anchor (use `content: ""` to delete the line) |
| `replace_lines` | `start_anchor`, `end_anchor`, `content` | Replace a range of lines (can expand or shrink) |
| `insert_after` | `anchor`, `content` | Insert new lines after the anchor line |
| `insert_before` | `anchor`, `content` | Insert new lines before the anchor line |
| `replace` | `old_content`, `new_content`, `allow_multiple` (optional) | Fallback string replacement; errors if 0 or 2+ matches (unless `allow_multiple: true`) |
| `append` | `content` | Append new lines to end of file (works for empty files too) |

## Error Handling

- Returns an error if the file doesn't exist
- Returns an error if any anchor hash doesn't match (stale read)
- Returns an error if a line number is out of range
- Returns an error if splice ranges overlap within a batch
- Returns an error if a `replace` op matches 0 or 2+ times (unless `allow_multiple: true`)
- Returns an error for unknown op types or invalid JSON
- All edits are validated before any writes occur (atomic): on any error the file is unchanged

## Notes

- Anchors are generated by `read_file(hashline=True)` and `grep_search(hashline=True)`
- The hash is a CRC32-based 4-char hex digest of the line content (with trailing spaces and tabs stripped; leading whitespace is included so indentation changes invalidate anchors). Collision probability is ~0.0015% per changed line.
- All anchor-based ops are validated before any writes occur; if any op fails validation, the file is left unchanged
- String `replace` ops are applied after all anchor-based splices, so they match against post-splice content
- Original line endings (LF or CRLF) are preserved
- The response includes the updated file content in hashline format, so subsequent edits can use the new anchors without re-reading
