Hashline Edit#
Snapshot-anchored file editing inspired by oh-my-pi’s Hashline format.
Provides the hashline_edit tool: a two-call editing cycle where the
read tool populates a snapshot store with a file-level 4-byte SHA-256 tag,
and hashline_edit verifies that tag before applying line-range operations.
Workflow#
read <path>— returns the file with a[PATH#TAG]header on the first line of the code block body. The snapshot is stored in-memory.hashline_edit <path>— the code block body must begin with[PATH#TAG]matching the tag seen in step 1, then list one or morePUT/CUToperations.
Edit syntax#
[PATH#TAG]
PUT N.=M: — replace lines N through M (inclusive) with new content
+new line 1
+new line 2
PUT <N: — insert the new content BEFORE line N
+new line
PUT >N: — insert the new content AFTER line N
+new line
PUT N*: — replace the syntactic block starting at line N
+new content
CUT N.=M — delete lines N through M (no content block follows)
CUT N.=M @r — delete and save to named register r
PUT >N @r — paste register r content AFTER line N (no content block)
PUT <N @r — paste register r content BEFORE line N (no content block)
PUT N.=M: @r — replace lines N–M with register r content (no content block)
Content lines are prefixed with +. An operation’s content block ends at
the next operation header or end-of-input. Register-paste operations have no
content block.
If the live file’s hash no longer matches the stored snapshot tag the entire edit is rejected with a clear error — no silent corruption.
Instructions
Use after ``read`` to apply precise, safe line edits without re-reading the full file.
The tag from ``read`` anchors your edits to the exact version you saw — any external
change to the file on disk is detected and rejected before a single byte is written.
Prefer this tool over ``patch`` when you have exact line numbers and want to avoid
repeating large context blocks. You can chain multiple operations in one call;
line numbers always reference the version ``read`` showed you.
### Workflow
1. Call ``read <path>`` — the output starts with ``[PATH#TAG]`` on the first line
inside the code block. Remember this TAG.
2. Write a ``hashline_edit <path>`` block beginning with the same ``[PATH#TAG]``
header, followed by one or more operations.
### Recovery from rejection
If the file changed between ``read`` and your edit, you will see:
``hashline_edit: file has changed since snapshot was captured…``
Re-read the file with ``read`` to get a new tag and restate your operations.
### Operations
| Syntax | Effect |
|-------------------|--------------------------------------------------|
| ``PUT N.=M:`` | Replace lines N through M with new lines |
| ``PUT N*:`` | Replace the syntactic block at line N |
| ``PUT <N:`` | Insert new lines BEFORE line N |
| ``PUT >N:`` | Insert new lines AFTER line N |
| ``CUT N.=M`` | Delete lines N through M |
| ``CUT N.=M @r`` | Delete lines N–M and save to register *r* |
| ``PUT N.=M: @r`` | Replace lines N–M with content of register *r* |
| ``PUT <N @r`` | Insert register *r* content BEFORE line N |
| ``PUT >N @r`` | Insert register *r* content AFTER line N |
``PUT N*:`` replaces a whole indented construct — point it at the ``def``,
``class``, ``if``, ``for``, ``while``, or ``try`` line and the system finds the
closing line for you, including any ``elif``/``else``/``except``/``finally``
clauses attached to that same statement. Use it instead of ``PUT N.=M:`` when
you want to replace an entire function/block and don't want to count lines by
hand.
Point N at the actual header line (e.g. the ``def`` line, not a decorator or a
comment above it) — the block is resolved from that line's indentation.
Constructs the heuristic can't reliably reproduce: blocks that mix tabs and
spaces, or a header followed by a same-indentation line that is *not* a
continuation clause (e.g. a one-line ``if x: y`` body) — for those, fall back to
``PUT N.=M:`` with an explicit line range.
**Registers** avoid reproducing unchanged content when moving or copying exact
lines within one edit call, reducing tokens and transcription mistakes.
``CUT N.=M @r`` captures deleted lines into register *r* (a short word), then
``PUT >N @r`` or ``PUT <N @r`` pastes them. Registers are resolved from the
original file state, so a cut at line 5 and a paste at line 20 work correctly
regardless of apply order. The PUT destination must be outside the CUT range.
New content lines are prefixed with ``+``. An empty line ends a content block.
CUT (with or without a register) has no content block. Register-paste operations
(``PUT ... @r``) also have no content block. Line numbers reference the version
shown by ``read``.
### Example
```
[greet.py#A1B2C3D4]
PUT 2.=3:
+ print(f"Hi, {name}")
CUT 4.=4
PUT >5:
+ return name
```
### Register example (move a function to the bottom)
```
[module.py#A1B2C3D4]
CUT 3.=10 @fn
PUT >40 @fn
```
Examples
| User |
update greet.py to use an f-string |
| Assistant |
First, read the file: |
| System |
```greet.py > [greet.py#A1B2C3D4] > 1\tdef greet(name): > 2\t msg = "Hello, " + name > 3\t print(msg) > ``` |
| Assistant |
|
| System |
hashline_edit applied to `greet.py` (1 operation) |
- class gptme.tools.hashline_edit.HashlineOp
A single parsed edit operation.
- exception gptme.tools.hashline_edit.ParseError