@oh-my-pi-zen/hashline 16.3.6-zen.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +359 -0
- package/README.md +82 -0
- package/dist/types/apply.d.ts +10 -0
- package/dist/types/block.d.ts +39 -0
- package/dist/types/diff-preview.d.ts +14 -0
- package/dist/types/format.d.ts +83 -0
- package/dist/types/fs.d.ts +109 -0
- package/dist/types/index.d.ts +17 -0
- package/dist/types/input.d.ts +110 -0
- package/dist/types/messages.d.ts +147 -0
- package/dist/types/mismatch.d.ts +44 -0
- package/dist/types/normalize.d.ts +20 -0
- package/dist/types/parser.d.ts +27 -0
- package/dist/types/patcher.d.ts +118 -0
- package/dist/types/prefixes.d.ts +42 -0
- package/dist/types/recovery.d.ts +44 -0
- package/dist/types/snapshots.d.ts +114 -0
- package/dist/types/stream.d.ts +2 -0
- package/dist/types/tokenizer.d.ts +73 -0
- package/dist/types/types.d.ts +172 -0
- package/package.json +64 -0
- package/src/apply.ts +1281 -0
- package/src/block.ts +168 -0
- package/src/diff-preview.ts +124 -0
- package/src/format.ts +141 -0
- package/src/fs.ts +246 -0
- package/src/grammar.lark +29 -0
- package/src/index.ts +17 -0
- package/src/input.ts +462 -0
- package/src/messages.ts +312 -0
- package/src/mismatch.ts +118 -0
- package/src/normalize.ts +38 -0
- package/src/parser.ts +456 -0
- package/src/patcher.ts +655 -0
- package/src/prefixes.ts +142 -0
- package/src/prompt.md +172 -0
- package/src/recovery.ts +417 -0
- package/src/snapshots.ts +255 -0
- package/src/stream.ts +132 -0
- package/src/tokenizer.ts +557 -0
- package/src/types.ts +172 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
## [16.3.3] - 2026-07-02
|
|
6
|
+
|
|
7
|
+
### Breaking Changes
|
|
8
|
+
|
|
9
|
+
- Removed SnapshotStore.byHashExact. Consumers should now use byHash, which resolves collisions by returning the most recently recorded version.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Improved patch application robustness by resolving 16-bit snapshot tag collisions to the most recent version instead of rejecting them.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Fixed frequent edit rejections after a structural-summary read (affecting parseable code over 100 lines) by automatically inlining unseen anchor lines and merging them into the snapshot's seen lines, allowing immediate retries to succeed without requiring a separate range re-read.
|
|
18
|
+
|
|
19
|
+
## [16.3.0] - 2026-07-02
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Significantly improved performance on large files by optimizing stale-anchor remap validation.
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- Fixed an issue where snapshot tag collisions could cause line-anchored edits to be incorrectly applied to unrelated content, improving recovery and edit-preview safety.
|
|
28
|
+
- Fixed tracking of edit anchors when earlier in-session insertions or deletions shift unchanged target lines.
|
|
29
|
+
- Fixed hashline edit guidance and parsing errors for Markdown list rows.
|
|
30
|
+
|
|
31
|
+
## [16.2.8] - 2026-06-30
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- Fixed hashline writes preserving UTF-8 BOM bytes when the host text decoder hides the leading `U+FEFF`. ([#3867](https://github.com/cagedbird043/oh-my-pi-zen/issues/3867))
|
|
36
|
+
|
|
37
|
+
## [16.2.6] - 2026-06-29
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- Fixed a parser error ("payload line has no preceding hunk header") caused by stray dots before the trailing colon in hunk headers, improving compatibility with GLM 5.2 outputs.
|
|
42
|
+
|
|
43
|
+
## [16.2.0] - 2026-06-27
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
|
|
47
|
+
- Added `REM` (remove) and `MV` (move/rename) section operations to hashline patches, allowing files to be deleted or relocated (with snapshot history migration) directly within the edit tool.
|
|
48
|
+
|
|
49
|
+
## [16.1.23] - 2026-06-26
|
|
50
|
+
|
|
51
|
+
### Added
|
|
52
|
+
|
|
53
|
+
- Updated prompt documentation to include support for Markdown section operations
|
|
54
|
+
|
|
55
|
+
### Fixed
|
|
56
|
+
|
|
57
|
+
- Improved file path recovery to correctly handle read-only or incorrectly typed paths
|
|
58
|
+
|
|
59
|
+
## [16.1.14] - 2026-06-22
|
|
60
|
+
|
|
61
|
+
### Fixed
|
|
62
|
+
|
|
63
|
+
- Improved delimiter-balance repair to correctly identify and spare deleted structural closers
|
|
64
|
+
- Prevented premature deletion of structural closers when existing code below the range covers them
|
|
65
|
+
- Accurate tracking of inserted lines to improve boundary repair logic for surrounding code blocks
|
|
66
|
+
- Fixed delimiter-balance repair so deleted closer suffixes are kept only when the replacement prefix still has unmatched openers for them, avoiding duplicated trailing braces while preserving omitted outer closers.
|
|
67
|
+
|
|
68
|
+
## [16.1.8] - 2026-06-20
|
|
69
|
+
|
|
70
|
+
### Fixed
|
|
71
|
+
|
|
72
|
+
- Fixed multi-hunk delimiter-balance repair so a `SWAP` that drops a structural closer no longer keeps it when another hunk already removed the matching opener (a deliberate wrapper removal); the missing-closer repair now weighs each group against the whole patch's residual delimiter balance — summed per hunk so quote/comment state never bleeds across non-contiguous hunks — and consumes that residual per repair so a genuine missing closer elsewhere still fires. ([#3142](https://github.com/cagedbird043/oh-my-pi-zen/issues/3142))
|
|
73
|
+
|
|
74
|
+
## [16.1.2] - 2026-06-19
|
|
75
|
+
|
|
76
|
+
### Changed
|
|
77
|
+
|
|
78
|
+
- Refined documentation and prompt instructions for clarity and brevity
|
|
79
|
+
|
|
80
|
+
## [16.0.2] - 2026-06-16
|
|
81
|
+
|
|
82
|
+
### Fixed
|
|
83
|
+
|
|
84
|
+
- Auto-repaired duplicated JSX/XML closing boundary lines at the end of single-line replacement expansions. ([#2705](https://github.com/cagedbird043/oh-my-pi-zen/issues/2705))
|
|
85
|
+
|
|
86
|
+
## [16.0.1] - 2026-06-15
|
|
87
|
+
|
|
88
|
+
### Fixed
|
|
89
|
+
|
|
90
|
+
- Auto-repaired one-sided multi-line boundary echoes by dropping delimiter-neutral duplicated boundary lines and emitted a boundary-echo warning
|
|
91
|
+
- Parser now treats a leading `\` on inline payload bodies as the payload delimiter, matching standalone payload rows.
|
|
92
|
+
- Restored the warning emitted when escaped indented payload rows (`\\ TEXT`) are accepted as payload delimiters.
|
|
93
|
+
|
|
94
|
+
## [15.13.3] - 2026-06-15
|
|
95
|
+
|
|
96
|
+
### Changed
|
|
97
|
+
|
|
98
|
+
- Changed the recommended hashline range separator from `..` to `.=` (e.g. `SWAP 1.=3:`, `DEL 4.=5`) so the inclusive `<=`-style end is self-evident. `HL_RANGE_SEP` is now `.=`; the prompt, grammar, error messages, and emitted headers all use it. The lenient parser still accepts the legacy `..` (and `-`/`…`/space) forms.
|
|
99
|
+
|
|
100
|
+
## [15.13.2] - 2026-06-15
|
|
101
|
+
|
|
102
|
+
### Breaking Changes
|
|
103
|
+
|
|
104
|
+
- Renamed all hashline DSL operators to concise abbreviated keywords:
|
|
105
|
+
- `replace` -> `SWAP`
|
|
106
|
+
- `delete` -> `DEL`
|
|
107
|
+
- `insert before`/`after`/`head`/`tail` -> `INS.PRE`/`POST`/`HEAD`/`TAIL`
|
|
108
|
+
- `replace_block` -> `SWAP.BLK`
|
|
109
|
+
- `delete_block` -> `DEL.BLK`
|
|
110
|
+
- `insert_after_block` -> `INS.BLK.POST`
|
|
111
|
+
|
|
112
|
+
## [15.13.1] - 2026-06-15
|
|
113
|
+
|
|
114
|
+
### Breaking Changes
|
|
115
|
+
|
|
116
|
+
- Rejected edits anchored to lines not displayed in the tagged read/search output, requiring unseen ranges to be re-read before reapplying
|
|
117
|
+
|
|
118
|
+
### Changed
|
|
119
|
+
|
|
120
|
+
- Rejected `replace block`, `delete block`, and `insert after block` operations that resolve to a single line and instructed users to use the plain single-line form or anchor the true construct opener
|
|
121
|
+
|
|
122
|
+
### Fixed
|
|
123
|
+
|
|
124
|
+
- Normalized cwd-relative hashline paths to forward-slash form on Windows.
|
|
125
|
+
|
|
126
|
+
## [15.12.5] - 2026-06-13
|
|
127
|
+
|
|
128
|
+
### Fixed
|
|
129
|
+
|
|
130
|
+
- Fixed delimiter-balance boundary repair so it does not keep a deleted structural closer when the replacement payload already restates that closer.
|
|
131
|
+
|
|
132
|
+
## [15.12.0] - 2026-06-12
|
|
133
|
+
|
|
134
|
+
### Changed
|
|
135
|
+
|
|
136
|
+
- Condensed all parser/applier/patcher error and warning messages: shorter wording, same diagnostic anchors (op names, line numbers, suggested fallback forms)
|
|
137
|
+
|
|
138
|
+
## [15.11.4] - 2026-06-12
|
|
139
|
+
|
|
140
|
+
### Added
|
|
141
|
+
|
|
142
|
+
- Added inward landing correction for `insert after block N:`: a body indented deeper than the block's closing line now slides back across the block's trailing closer lines and lands inside the block at its claimed depth, with a warning naming the landing line. Same conservative guards as the outward shift — comparable indentation only, closers only, abandoned when another hunk targets a crossed line; plain `insert after M:` stays literal
|
|
143
|
+
- Added closer-anchor lowering for `insert after block N:`: anchoring on a pure closing-delimiter line (where no block begins, so resolution previously failed the whole patch) now applies as plain `insert after N:` with a warning teaching the opener-only rule. `resolveBlockEdits` gained an `onWarning` callback; apply, preview, and patcher paths surface it on `warnings`
|
|
144
|
+
|
|
145
|
+
### Changed
|
|
146
|
+
|
|
147
|
+
- Condensed the edit-tool prompt: one-line op definitions, 5–20-word rules, and a tighter `<critical>` recap; landing-correction mechanics are no longer described to the agent
|
|
148
|
+
|
|
149
|
+
## [15.11.1] - 2026-06-11
|
|
150
|
+
|
|
151
|
+
### Fixed
|
|
152
|
+
|
|
153
|
+
- Fixed the `insert after block N:` prompt guidance so it explicitly says N must be the block opener, not the closing delimiter or last visible line, and points visible closing-line edits to plain `insert after M:`. ([#2292](https://github.com/cagedbird043/oh-my-pi-zen/issues/2292))
|
|
154
|
+
|
|
155
|
+
## [15.11.0] - 2026-06-10
|
|
156
|
+
|
|
157
|
+
### Changed
|
|
158
|
+
|
|
159
|
+
- Block-unresolved errors (`replace block N:` / `delete block N` / `insert after block N:` failing to resolve a syntactic block) now append a numbered preview of the file around the anchor line — same `*`-marked context rows the hash-mismatch error shows — so the offending line is visible without a re-read
|
|
160
|
+
|
|
161
|
+
## [15.10.11] - 2026-06-10
|
|
162
|
+
|
|
163
|
+
### Breaking Changes
|
|
164
|
+
|
|
165
|
+
- Changed `BlockResolution.isDelete` to `BlockResolution.op` (`"replace" | "delete" | "insert_after"`) so resolutions can describe every block-anchored op
|
|
166
|
+
|
|
167
|
+
### Added
|
|
168
|
+
|
|
169
|
+
- Added `insert after block N:` patch syntax to insert body rows after the last line of the tree-sitter-resolved block beginning on line N, so a statement can be placed after a construct without counting to its closing line
|
|
170
|
+
- Added depth-guided landing correction for `insert after N:` hunks: a body indented shallower than its anchor line slides past the structural closer lines below the anchor until depth returns to the body's level, with a warning naming the final landing line. The shift never crosses content lines, skips incomparable indentation styles and pure-closer bodies, and is abandoned when another hunk targets a crossed line
|
|
171
|
+
- Added a global byte ceiling to `InMemorySnapshotStore` (`maxTotalBytes`, default 64 MiB): the cap was previously per-file only, so a session reading many large files retained up to 30 paths × 4 full-text versions indefinitely
|
|
172
|
+
|
|
173
|
+
### Changed
|
|
174
|
+
|
|
175
|
+
- Trimmed the `replace block N:` ops entry in the patch prompt to grammar and pointing rules; the usage doctrine it duplicated stays in the rules section
|
|
176
|
+
- Changed `buildCompactDiffPreview` to treat blank rows as gap separators alongside `…` markers: separators never stack (removed lines omitted from the preview no longer leave two adjacent), and leading/trailing separators are trimmed
|
|
177
|
+
|
|
178
|
+
### Fixed
|
|
179
|
+
|
|
180
|
+
- Fixed the boundary-echo repair stripping payload edges without the balance-neutrality guard its own documentation promised: in brace-heavy code where bare `}` lines repeat, a payload intentionally beginning/ending with lines identical to the range's neighbors had both edges silently dropped, writing content that differed from what was authored
|
|
181
|
+
- Fixed lenient bare-body handling silently mutating payloads: interior blank rows in an un-prefixed body were dropped outright, and a body of numeric-keyed literals (`1: "one"` dict/YAML shapes) satisfied the uniform line-prefix check and had its keys stripped from every line — blank rows are now preserved when proven interior, and the uniform strip refuses lone-literal remainders
|
|
182
|
+
- Fixed the multi-section "all-or-nothing" claim being false for write failures: commits run serially, so a mid-batch write error left earlier sections on disk while the thrown error said nothing — the error now lists exactly which sections were written and which were not
|
|
183
|
+
- Fixed `delete`/`replace` ranges ending on the phantom trailing line of a newline-terminated file silently stripping the file's final newline; such anchors are now rejected with guidance toward `N-1` / `insert tail:` (inserts there remain valid, and genuine empty last lines of unterminated files stay deletable)
|
|
184
|
+
|
|
185
|
+
## [15.10.5] - 2026-06-08
|
|
186
|
+
|
|
187
|
+
### Added
|
|
188
|
+
|
|
189
|
+
- Added `maxAddedRunContext` option to control how many added lines are shown at each side of collapsed inserted runs, with `maxUnchangedRun` kept as a backward-compatible alias
|
|
190
|
+
|
|
191
|
+
### Changed
|
|
192
|
+
|
|
193
|
+
- Changed `buildCompactDiffPreview` to omit removed lines from the preview while preserving removal counts for offset tracking
|
|
194
|
+
- Changed `buildCompactDiffPreview` to collapse long contiguous added runs with a bare `…` marker, keeping only the first and last `maxAddedRunContext` lines visible (the surrounding line numbers convey how many were elided)
|
|
195
|
+
|
|
196
|
+
### Fixed
|
|
197
|
+
|
|
198
|
+
- Fixed compact edit previews to omit deleted content, keep visible lines anchored to the current file, and collapse long inserted runs with a bare `…` elision marker.
|
|
199
|
+
- Fixed compact edit previews to render added/current lines without diff-prefix padding and normalize adjacent ASCII/Unicode elision markers to one `…`.
|
|
200
|
+
|
|
201
|
+
## [15.10.3] - 2026-06-08
|
|
202
|
+
|
|
203
|
+
### Added
|
|
204
|
+
|
|
205
|
+
- Added a `BlockResolution` type and surfaced resolved block spans on `ApplyResult.blockResolutions` / `PatchSectionResult.blockResolutions`. `resolveBlockEdits` now accepts an `onResolved` callback that reports each `replace block N:` / `delete block N` anchor's resolved `[start, end]` span (and whether it was a delete). Spans are surfaced only on the no-drift apply paths, where the resolved line numbers line up with the tag the caller read.
|
|
206
|
+
|
|
207
|
+
### Changed
|
|
208
|
+
|
|
209
|
+
- Reworked the `edit` tool prompt (`prompt.md`): added a `replace block N` vs `replace N..M` decision rule, documented that a leading decorator/attribute/doc-comment is a separate node not swept into the block (point N at the first decorator line, or use `replace N..M` for a Rust-style `///` sibling comment), reframed the blast-radius guidance so "block replace" no longer reads as the dangerous option, and added a decorated-definition example.
|
|
210
|
+
|
|
211
|
+
## [15.10.2] - 2026-06-08
|
|
212
|
+
|
|
213
|
+
### Fixed
|
|
214
|
+
|
|
215
|
+
- Stripped read-output line-number prefixes (`N:`) from auto-piped bare body rows so that pasting `3:text` without a `+` prefix no longer injects `3:` as literal content. Stripping is applied only when *every* bare row in the hunk carries the prefix (the signature of a pasted snapshot) and removes at most one prefix per row, so a genuine body that merely starts with `digits:` (YAML port maps, timestamps) is left intact ([#1492](https://github.com/cagedbird043/oh-my-pi-zen/issues/1492)).
|
|
216
|
+
|
|
217
|
+
## [15.9.67] - 2026-06-06
|
|
218
|
+
|
|
219
|
+
### Breaking Changes
|
|
220
|
+
|
|
221
|
+
- Changed hashline file section headers from `¶PATH#TAG` to `[PATH#TAG]` so model-authored edits use ASCII delimiters instead of a pilcrow sigil.
|
|
222
|
+
|
|
223
|
+
### Fixed
|
|
224
|
+
|
|
225
|
+
- Fixed missing-header diagnostics and copied-content prefix stripping to consistently teach and recognize 4-hex snapshot tags.
|
|
226
|
+
|
|
227
|
+
## [15.8.2] - 2026-06-03
|
|
228
|
+
|
|
229
|
+
### Fixed
|
|
230
|
+
|
|
231
|
+
- Fixed delimiter-balance boundary repair to also drop a single duplicated structural opener (e.g. a restated `foo(` / `if (x) {` signature line surviving just above the range), not only duplicated closers. Zero-balance duplicates remain untouched.
|
|
232
|
+
|
|
233
|
+
## [15.8.0] - 2026-06-02
|
|
234
|
+
|
|
235
|
+
### Fixed
|
|
236
|
+
|
|
237
|
+
- Fixed hashline replacements that accidentally restated unchanged lines above and below the selected range so they no longer duplicate both boundary lines ([#1664](https://github.com/cagedbird043/oh-my-pi-zen/issues/1664)).
|
|
238
|
+
|
|
239
|
+
## [15.7.0] - 2026-05-31
|
|
240
|
+
|
|
241
|
+
### Added
|
|
242
|
+
|
|
243
|
+
- Added `replace block N:` and `delete block N` patch syntax to replace or delete the entire syntactic block that begins on line N using tree-sitter-resolved spans
|
|
244
|
+
- Added `BlockResolver` support in `Patcher` and `PatchSection.applyTo`/`applyPartialTo` to wire language-specific block-resolution at apply time
|
|
245
|
+
- Added `resolveBlockEdits` and block edit type definitions to the package API for resolving deferred `replace block` / `delete block` edits
|
|
246
|
+
|
|
247
|
+
## [15.5.13] - 2026-05-29
|
|
248
|
+
|
|
249
|
+
### Breaking Changes
|
|
250
|
+
|
|
251
|
+
- Changed hashline section tags from 3-hex to 4-hex content-hash tags, so legacy 3-digit tags are no longer valid
|
|
252
|
+
- Changed hashline syntax to verb-based v4: body-bearing ops are `replace N..M:`, `insert before N:`, `insert after N:`, `insert head:`, and `insert tail:`, while bodyless `delete N..M` handles deletion. Removed `>A..B` repeat rows and the old `prepend:` / `append:` virtual insert headers; `-` rows remain rejected with a teaching error.
|
|
253
|
+
|
|
254
|
+
### Added
|
|
255
|
+
|
|
256
|
+
- Added `maxPaths` and `maxVersionsPerPath` options to `InMemorySnapshotStore` to bound tracked paths and per-path snapshot history
|
|
257
|
+
- Re-introduced balance-validated boundary repair in `applyEdits`. A replacement hunk (`replace N..M:` + body) is normalized so its payload preserves the deleted region's delimiter balance: when the body restates a closing delimiter that survives just outside the range (duplicate `}` / `);` / `]`) the echo is dropped, and when the range deletes a structural closer the body never restates (missing closer) the closer is spared instead of deleted. A repair fires only when one boundary operation drives the per-channel `()` / `[]` / `{}` imbalance to exactly zero while leaving surrounding text byte-identical (single-line ops are limited to pure structural-closer lines), so balance-preserving edits and intentional balanced duplicates are never touched. Bracket counting skips strings, template literals, and comments. Each repair surfaces a `delimiter-balance` warning through `ApplyResult.warnings`.
|
|
258
|
+
|
|
259
|
+
### Changed
|
|
260
|
+
|
|
261
|
+
- Changed patch application to accept edits whenever the live file's normalized content hash matches the section tag, even when that anchor was not covered by a stored snapshot
|
|
262
|
+
|
|
263
|
+
### Removed
|
|
264
|
+
|
|
265
|
+
- Removed `SnapshotStore.recordContiguous` and `SnapshotStore.recordSparse` in favor of full-file `record(path, fullText)` snapshots
|
|
266
|
+
|
|
267
|
+
### Fixed
|
|
268
|
+
|
|
269
|
+
- Fixed hash mismatch rejections caused by CRLF or trailing spaces/tabs by normalizing those characters before computing file-hash tags
|
|
270
|
+
|
|
271
|
+
## [15.5.12] - 2026-05-29
|
|
272
|
+
|
|
273
|
+
### Changed
|
|
274
|
+
|
|
275
|
+
- `InMemorySnapshotStore` now coalesces consecutive same-path reads into one tag whenever their views agree on every shared line. Overlapping or directly abutting range reads extend the existing snapshot's contiguous run in place; reads separated by a gap union into a `SparseSnapshot` spanning both ranges. A disagreeing shared line is treated as "the file changed on disk" and mints a fresh tag, preserving the prior superset-dedup behavior. This stops sequential range reads of an unchanged file (e.g. `:50-100` then `:100-200`, or `:1-100` then `:150-200`) from fragmenting into separate anchors.
|
|
276
|
+
|
|
277
|
+
## [15.5.11] - 2026-05-29
|
|
278
|
+
|
|
279
|
+
### Added
|
|
280
|
+
|
|
281
|
+
- `MismatchError` now distinguishes "hash recognized but file content drifted" from "hash never recorded for this path". The latter (likely fabricated or carried over from a prior session) emits a dedicated `hash #X is not from this session` rejection message with explicit "never invent the tag" guidance. The `MismatchDetails` interface gains an optional `hashRecognized?: boolean` (defaults to `true` for backward compatibility); `MismatchError` exposes it as a readonly field so callers can branch on the cause.
|
|
282
|
+
|
|
283
|
+
## [15.5.8] - 2026-05-28
|
|
284
|
+
|
|
285
|
+
### Breaking Changes
|
|
286
|
+
|
|
287
|
+
- Removed the single-number hunk header shorthand. A hunk header now REQUIRES two line numbers (`A A` for a single line, `A B` for a range); a bare `A` row throws `single-number hunk header "A" is no longer accepted`. The `&A` body-row shorthand for `&A..A` is unchanged.
|
|
288
|
+
- Changed hunk header syntax from `A-B:` to `@@ A..B @@` with `@@ A @@` shorthand for single lines
|
|
289
|
+
- Changed repeat payload sigil from `^A-B` to `&A..B` with `&A` shorthand for single lines
|
|
290
|
+
- Changed range separator from `-` to `..` in all contexts (anchors and repeats)
|
|
291
|
+
- Changed empty hunk behavior: concrete ranges now delete (no blank-line insertion); BOF/EOF empty hunks are now no-ops
|
|
292
|
+
- Removed `ApplyOptions` parameter from `applyEdits()` and related APIs; auto-absorb behavior is no longer configurable
|
|
293
|
+
- Removed diagnostic warnings for auto-absorbed duplicates from `ApplyResult`; warnings now come only from parser, patcher, or recovery
|
|
294
|
+
- Removed legacy hashline block syntax `A-B:`, `A-B:-`, and `^A-B` and replaced edits with `@@ A..B @@` hunks using `+` and `&` body rows
|
|
295
|
+
- Removed `A:` shorthand syntax; use explicit `A-A:` for single-line anchors
|
|
296
|
+
- Removed `↑` and `↓` payload sigils; use `|TEXT` for literal rows and `^A-B` for repeating original lines
|
|
297
|
+
- Removed standalone delete rows; use inline `A-B:-` syntax instead
|
|
298
|
+
- Removed `after_anchor` cursor kind; all inserts now use `before_anchor` positioning
|
|
299
|
+
- Replaced insert-above/insert-below payload sigils with linear body rows: `|TEXT` emits literal text and `^A-B` repeats original file lines inline.
|
|
300
|
+
- Replaced standalone delete rows with inline range deletes: use `A-B:-`.
|
|
301
|
+
- Changed empty `A-B:`, `BOF:`, and `EOF:` blocks to write one blank line instead of being rejected.
|
|
302
|
+
|
|
303
|
+
### Added
|
|
304
|
+
|
|
305
|
+
- Added compatibility parsing for apply_patch-style and unified-diff row noise by stripping path noise and converting context/delete body rows into hashline-compatible operations with warnings
|
|
306
|
+
- Added `A-B:-` inline delete syntax for concrete range anchors
|
|
307
|
+
- Added `^A-B` repeat payload syntax to emit original file lines inline
|
|
308
|
+
- Added support for empty anchor blocks to write one blank line at the anchor position
|
|
309
|
+
|
|
310
|
+
### Changed
|
|
311
|
+
|
|
312
|
+
- Changed unified-diff compatibility mode to silently drop `-old` rows and convert context rows to `+TEXT` literals with a warning instead of rejecting them
|
|
313
|
+
- Changed `ABORT_MARKER` behavior to terminate parsing without surfacing a warning
|
|
314
|
+
- Changed numeric ranges to `A..B` form and accepted `@@ A @@` as shorthand for `@@ A..A @@`
|
|
315
|
+
- Changed empty hunk behavior so a concrete empty hunk deletes the selected range and `BOF`/`EOF` empty hunks no longer insert a blank line
|
|
316
|
+
- Changed parse behavior for `*** Abort` to stop processing without returning a speculative truncation warning
|
|
317
|
+
- Changed payload row format from three sigils (`|`, `↑`, `↓`) to two (`|`, `^`)
|
|
318
|
+
- Changed range anchor syntax to require explicit `A-B` form (no single-line shorthand)
|
|
319
|
+
- Changed error messages to reference new syntax and remove references to removed sigils
|
|
320
|
+
|
|
321
|
+
## [15.5.5] - 2026-05-27
|
|
322
|
+
|
|
323
|
+
### Breaking Changes
|
|
324
|
+
|
|
325
|
+
- Changed hashline payload continuations from `+TEXT` to `\TEXT`; use `\` for an explicit blank payload line.
|
|
326
|
+
|
|
327
|
+
### Added
|
|
328
|
+
|
|
329
|
+
- Added `parsePatchStreaming(diff)` and `PatchSection.applyPartialTo(text, options)` for incremental diff previews. Both tolerate a trailing in-flight op (no payload yet, or a per-token parse error mid-stream) instead of throwing or emitting a phantom empty-payload edit.
|
|
330
|
+
- Added `Executor.endStreaming()` — sibling of `end()` that drops a pending op with no accumulated payload rather than flushing it.
|
|
331
|
+
|
|
332
|
+
### Fixed
|
|
333
|
+
|
|
334
|
+
- Parser now skips markdown-style `# ...` lines when they directly precede a hashline operation, making model-generated explanatory rows in prompt examples non-blocking.
|
|
335
|
+
|
|
336
|
+
### Removed
|
|
337
|
+
|
|
338
|
+
- Removed the `A-B!` / `A!` deletion operator. Use `A-B:` with the desired payload (or empty payload to blank the range) instead.
|
|
339
|
+
|
|
340
|
+
All notable changes to this package will be documented in this file.
|
|
341
|
+
|
|
342
|
+
## [15.5.4] - 2026-05-27
|
|
343
|
+
|
|
344
|
+
### Added
|
|
345
|
+
|
|
346
|
+
- Added a high-level `Patcher` API with all-or-nothing `apply` and staged `prepare`/`commit` flows for multi-file patch updates
|
|
347
|
+
- Added pluggable `Filesystem` and `SnapshotStore` abstractions with built-in `NodeFilesystem`, `InMemoryFilesystem`, and `InMemorySnapshotStore` adapters
|
|
348
|
+
- Added patch parsing that consumes `¶PATH#HASH` hunk headers, validates section file hashes, and supports optional patch envelope markers
|
|
349
|
+
- Added tolerant input handling that strips read/search prefixes and supports optional `cwd`/fallback-path resolution when parsing patch payloads
|
|
350
|
+
- Added automatic line-ending and BOM normalization on read, with original encoding shape restored on write
|
|
351
|
+
- Added follow-up helpers `buildCompactDiffPreview` and `streamHashLines` for compact diff previews and chunked streaming of numbered lines
|
|
352
|
+
- Added stale-file-hash recovery that replays edits against snapshots and merges results onto current file content when direct hash validation fails
|
|
353
|
+
- Initial standalone release. Extracted from `@oh-my-pi-zen/pi-coding-agent`.
|
|
354
|
+
|
|
355
|
+
### Fixed
|
|
356
|
+
|
|
357
|
+
- Fixed repeated patch application mutating cached `after_anchor` edits between target snapshots
|
|
358
|
+
- Fixed multi-section patching to preflight write policies and reject duplicate canonical targets before any section is committed
|
|
359
|
+
- Fixed mixed line-ending restoration to preserve the first newline style instead of rewriting ties to LF
|
package/README.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# @oh-my-pi-zen/hashline
|
|
2
|
+
|
|
3
|
+
A compact, line-anchored patch language and applier.
|
|
4
|
+
|
|
5
|
+
Hashline is a diff format designed for LLM-driven file edits. It binds every
|
|
6
|
+
hunk to a file-content hash so stale anchors are rejected before they corrupt
|
|
7
|
+
code, and it abstracts over the filesystem so the same patcher works on disk,
|
|
8
|
+
in memory, over the network, or against any custom backend.
|
|
9
|
+
|
|
10
|
+
## Quick start
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import {
|
|
14
|
+
Filesystem,
|
|
15
|
+
InMemoryFilesystem,
|
|
16
|
+
InMemorySnapshotStore,
|
|
17
|
+
Patcher,
|
|
18
|
+
Patch,
|
|
19
|
+
} from "@oh-my-pi-zen/hashline";
|
|
20
|
+
|
|
21
|
+
const fs = new InMemoryFilesystem();
|
|
22
|
+
const snapshots = new InMemorySnapshotStore();
|
|
23
|
+
const before = `const greeting = "hi";\nexport { greeting };\n`;
|
|
24
|
+
await fs.writeText("hello.ts", before);
|
|
25
|
+
|
|
26
|
+
const tag = snapshots.record("hello.ts", before);
|
|
27
|
+
const patcher = new Patcher({ fs, snapshots });
|
|
28
|
+
const patch = Patch.parse(String.raw`[hello.ts#${tag}]
|
|
29
|
+
SWAP 1.=1:
|
|
30
|
+
+const greeting = "hello";`);
|
|
31
|
+
const result = await patcher.apply(patch);
|
|
32
|
+
|
|
33
|
+
console.log(result.sections[0].op); // "update"
|
|
34
|
+
console.log(await fs.readText("hello.ts"));
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Format
|
|
38
|
+
|
|
39
|
+
See [`src/prompt.md`](./src/prompt.md) for the user-facing description and
|
|
40
|
+
[`src/grammar.lark`](./src/grammar.lark) for the formal grammar.
|
|
41
|
+
|
|
42
|
+
Each file section starts with `[PATH#TAG]`. The tag is a 4-hex
|
|
43
|
+
content hash of the full normalized file text recorded by the
|
|
44
|
+
`SnapshotStore`, and it is not meaningful outside that store. The patcher
|
|
45
|
+
protects against stale anchors by resolving the tag, verifying the live file
|
|
46
|
+
still matches the recorded content hash, and refusing or attempting
|
|
47
|
+
session-aware recovery on mismatch.
|
|
48
|
+
|
|
49
|
+
Inside a section:
|
|
50
|
+
- `SWAP A.=B:` — replace lines A.=B with following `+TEXT` body rows.
|
|
51
|
+
- `SWAP.BLK A:` — replace the syntactic block beginning on line A.
|
|
52
|
+
- `DEL A.=B` / `DEL.BLK A` — delete concrete lines or a resolved block.
|
|
53
|
+
- `INS.PRE A:` / `INS.POST A:` / `INS.HEAD:` / `INS.TAIL:` — insert following body rows.
|
|
54
|
+
- `INS.BLK.POST A:` — insert following body rows after the resolved block's last line.
|
|
55
|
+
- `REM` — delete the whole file named by the section header.
|
|
56
|
+
- `MV DEST` — move/rename the section file to `DEST` (optionally after line edits).
|
|
57
|
+
- `+TEXT` — literal body row (use `+` alone for a blank line).
|
|
58
|
+
|
|
59
|
+
## Abstractions
|
|
60
|
+
|
|
61
|
+
### `Filesystem`
|
|
62
|
+
|
|
63
|
+
Read and write text by path. The default implementations:
|
|
64
|
+
|
|
65
|
+
- `InMemoryFilesystem` — backed by a `Map`. Tests, sandboxes.
|
|
66
|
+
- `NodeFilesystem` — disk-backed via `Bun.file`/`Bun.write`. Default for CLIs.
|
|
67
|
+
|
|
68
|
+
Subclass `Filesystem` to wire hashline into any storage: VFS, S3, an LSP
|
|
69
|
+
text-document protocol, a Git tree, anything.
|
|
70
|
+
|
|
71
|
+
### `SnapshotStore`
|
|
72
|
+
|
|
73
|
+
Required. Hashline tags are full-file content hashes recorded per path, so
|
|
74
|
+
`Patcher` must receive the store that observed them. Recovery replays edits
|
|
75
|
+
against the cached pre-edit snapshot and 3-way-merges onto current content
|
|
76
|
+
when the live file diverged.
|
|
77
|
+
|
|
78
|
+
### `Patcher`
|
|
79
|
+
|
|
80
|
+
The orchestration class. Reads, normalizes line endings + BOM, applies edits,
|
|
81
|
+
restores line endings, and writes via the configured `Filesystem`. Multi-section
|
|
82
|
+
patches are preflighted up front so a partial batch never lands.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ApplyResult, Edit } from "./types";
|
|
2
|
+
/** A line that is nothing but closing delimiters: `}`, `)`, `];`, `})`, `},`. */
|
|
3
|
+
export declare const STRUCTURAL_CLOSER_RE: RegExp;
|
|
4
|
+
/**
|
|
5
|
+
* Apply a parsed list of edits to a text body. Pure function — no I/O.
|
|
6
|
+
*
|
|
7
|
+
* Returns the post-edit text and the first changed line number (1-indexed).
|
|
8
|
+
* Throws if an anchor is out of bounds.
|
|
9
|
+
*/
|
|
10
|
+
export declare function applyEdits(text: string, edits: readonly Edit[]): ApplyResult;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { BlockResolution, BlockResolver, Edit } from "./types";
|
|
2
|
+
export interface ResolveBlockEditsOptions {
|
|
3
|
+
/**
|
|
4
|
+
* How to handle a replace/delete block edit that cannot be resolved
|
|
5
|
+
* (missing resolver or a `null` span). `"throw"` (default) raises a
|
|
6
|
+
* `blockUnresolvedMessage` error — used by the authoritative apply + final
|
|
7
|
+
* preview paths. `"drop"` silently skips the edit — used by the streaming
|
|
8
|
+
* preview, where a half-written file or transient parse error must not
|
|
9
|
+
* throw. Unresolvable `insert_after_block N:` edits never reach this: they
|
|
10
|
+
* are lowered to plain `insert after N:` with a warning.
|
|
11
|
+
*/
|
|
12
|
+
onUnresolved?: "throw" | "drop";
|
|
13
|
+
/**
|
|
14
|
+
* Invoked once per successfully resolved block edit, in patch order, with
|
|
15
|
+
* the anchor line and the concrete span it resolved to. Lets the host echo
|
|
16
|
+
* the resolution back to the caller. Never fired for dropped/unresolvable
|
|
17
|
+
* edits.
|
|
18
|
+
*/
|
|
19
|
+
onResolved?: (resolution: BlockResolution) => void;
|
|
20
|
+
/**
|
|
21
|
+
* Invoked once per diagnostic produced while resolving — currently the
|
|
22
|
+
* `insert_after_block N:` lowerings (closer anchor or unresolvable block).
|
|
23
|
+
* Hosts should surface these on the apply result's `warnings`.
|
|
24
|
+
*/
|
|
25
|
+
onWarning?: (message: string) => void;
|
|
26
|
+
}
|
|
27
|
+
/** True when at least one edit is an unresolved deferred block edit. */
|
|
28
|
+
export declare function hasBlockEdit(edits: readonly Edit[]): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Resolve every deferred block edit in `edits` against `text` (parsed as the
|
|
31
|
+
* language inferred from `path`). Non-block edits pass through untouched.
|
|
32
|
+
* Returns a fresh edit list with no `block` variants. The fast path returns the
|
|
33
|
+
* input unchanged when there is nothing to resolve.
|
|
34
|
+
*
|
|
35
|
+
* Synthesized inserts/deletes carry sequential `index` values for readability
|
|
36
|
+
* only — {@link applyEdits} re-derives every edit's index from array order, so
|
|
37
|
+
* the passthrough edits keeping their original indices is harmless.
|
|
38
|
+
*/
|
|
39
|
+
export declare function resolveBlockEdits(edits: readonly Edit[], text: string, path: string, resolver: BlockResolver | undefined, options?: ResolveBlockEditsOptions): readonly Edit[];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-number a unified diff that uses the `+<lineNum>|content` /
|
|
3
|
+
* `-<lineNum>|content` / ` <lineNum>|content` line format into a compact
|
|
4
|
+
* current-file preview. Removed lines are counted for stats and post-edit
|
|
5
|
+
* offset tracking, but omitted from the preview. Added and context lines are
|
|
6
|
+
* anchored to their post-edit positions so a follow-up edit can reuse visible
|
|
7
|
+
* concrete lines directly. Long contiguous added runs are summarized with a
|
|
8
|
+
* `…` marker instead of echoing every inserted line.
|
|
9
|
+
*
|
|
10
|
+
* This is intentionally decoupled from the diff producer: anything that
|
|
11
|
+
* emits the `<sign><lineNum>|<content>` shape works.
|
|
12
|
+
*/
|
|
13
|
+
import type { CompactDiffOptions, CompactDiffPreview } from "./types";
|
|
14
|
+
export declare function buildCompactDiffPreview(diff: string, options?: CompactDiffOptions): CompactDiffPreview;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hashline format primitives: sigils, separators, regex fragments, and
|
|
3
|
+
* display helpers. These are the single source of truth for the parser, the
|
|
4
|
+
* tokenizer, the prompt, and the formal grammar.
|
|
5
|
+
*/
|
|
6
|
+
import type { Cursor } from "./types";
|
|
7
|
+
/** File-section header delimiters: `[path#hash]`. */
|
|
8
|
+
export declare const HL_FILE_PREFIX = "[";
|
|
9
|
+
export declare const HL_FILE_SUFFIX = "]";
|
|
10
|
+
/** Payload sigil for literal body rows. */
|
|
11
|
+
export declare const HL_PAYLOAD_REPLACE = "+";
|
|
12
|
+
/** Hunk-header keyword for concrete line replacement. */
|
|
13
|
+
export declare const HL_REPLACE_KEYWORD = "SWAP";
|
|
14
|
+
/** Hunk-header keyword for concrete line deletion. */
|
|
15
|
+
export declare const HL_DELETE_KEYWORD = "DEL";
|
|
16
|
+
/** Hunk-header keyword for insertion operations. */
|
|
17
|
+
export declare const HL_INSERT_KEYWORD = "INS";
|
|
18
|
+
/** Insert position keyword for inserting before a concrete line. */
|
|
19
|
+
export declare const HL_INSERT_BEFORE = "PRE";
|
|
20
|
+
/** Insert position keyword for inserting after a concrete line. */
|
|
21
|
+
export declare const HL_INSERT_AFTER = "POST";
|
|
22
|
+
/** Insert position keyword for inserting at the start of the file. */
|
|
23
|
+
export declare const HL_INSERT_HEAD = "HEAD";
|
|
24
|
+
/** Insert position keyword for inserting at the end of the file. */
|
|
25
|
+
export declare const HL_INSERT_TAIL = "TAIL";
|
|
26
|
+
/** Hunk-header keyword: `SWAP.BLK N:` resolves N to a tree-sitter block range and replaces its span. */
|
|
27
|
+
export declare const HL_REPLACE_BLOCK_KEYWORD = "SWAP.BLK";
|
|
28
|
+
/** Hunk-header keyword: `DEL.BLK N` resolves N to a tree-sitter block range and deletes its span. */
|
|
29
|
+
export declare const HL_DELETE_BLOCK_KEYWORD = "DEL.BLK";
|
|
30
|
+
/** Hunk-header keyword: `INS.BLK.POST N:` inserts after the last line of the tree-sitter block at N. */
|
|
31
|
+
export declare const HL_INSERT_AFTER_BLOCK_KEYWORD = "INS.BLK.POST";
|
|
32
|
+
/** File-level keyword: `REM` deletes the whole file named by the section header. */
|
|
33
|
+
export declare const HL_REM_KEYWORD = "REM";
|
|
34
|
+
/** File-level keyword: `MV DEST` renames/moves the section file to `DEST`. */
|
|
35
|
+
export declare const HL_MOVE_KEYWORD = "MV";
|
|
36
|
+
export declare const HL_HEADER_COLON = ":";
|
|
37
|
+
/** Separator between a hashline file path and its opaque snapshot tag. */
|
|
38
|
+
export declare const HL_FILE_HASH_SEP = "#";
|
|
39
|
+
/** Separator between two line numbers in a range, e.g. `5.=10`. */
|
|
40
|
+
export declare const HL_RANGE_SEP = ".=";
|
|
41
|
+
/** Separator between a line number and displayed line content in hashline mode. */
|
|
42
|
+
export declare const HL_LINE_BODY_SEP = ":";
|
|
43
|
+
/** Bare positive line-number Lid (no decorations, no captures, no anchors). */
|
|
44
|
+
export declare const HL_LINE_RE_RAW = "[1-9]\\d*";
|
|
45
|
+
/** Capture-group form of {@link HL_LINE_RE_RAW}. */
|
|
46
|
+
export declare const HL_LINE_CAPTURE_RE_RAW = "([1-9]\\d*)";
|
|
47
|
+
/** Format a concrete replacement hunk header. */
|
|
48
|
+
export declare function formatReplaceHeader(start: number, end: number): string;
|
|
49
|
+
/** Format a concrete deletion hunk header. */
|
|
50
|
+
export declare function formatDeleteHeader(start: number, end?: number): string;
|
|
51
|
+
/** Format an insertion hunk header for a cursor position. */
|
|
52
|
+
export declare function formatInsertHeader(cursor: Cursor): string;
|
|
53
|
+
/** Number of hex characters in a content-derived file-hash tag. */
|
|
54
|
+
export declare const HL_FILE_HASH_LENGTH = 4;
|
|
55
|
+
/** Canonical uppercase hexadecimal content-hash tag carried by a hashline section header. */
|
|
56
|
+
export declare const HL_FILE_HASH_RE_RAW = "[0-9A-F]{4}";
|
|
57
|
+
/** Capture-group form of {@link HL_FILE_HASH_RE_RAW}. */
|
|
58
|
+
export declare const HL_FILE_HASH_CAPTURE_RE_RAW = "([0-9A-F]{4})";
|
|
59
|
+
/** Regex-escaped form of {@link HL_LINE_BODY_SEP}, safe for embedding inside a regex. */
|
|
60
|
+
export declare const HL_LINE_BODY_SEP_RE_RAW: string;
|
|
61
|
+
/**
|
|
62
|
+
* Representative file-hash tags for use in user-facing error messages and
|
|
63
|
+
* prompt examples.
|
|
64
|
+
*/
|
|
65
|
+
export declare const HL_FILE_HASH_EXAMPLES: readonly ["1A2B", "3C4D", "9F3E"];
|
|
66
|
+
/**
|
|
67
|
+
* Compute the content-derived hash tag carried by a hashline section header.
|
|
68
|
+
* The tag is a 4-hex fingerprint of the whole file's normalized text: any read
|
|
69
|
+
* of byte-identical content mints the same tag, and a follow-up edit anchored
|
|
70
|
+
* at any line validates whenever the live file still hashes to it.
|
|
71
|
+
*/
|
|
72
|
+
export declare function computeFileHash(text: string): string;
|
|
73
|
+
/**
|
|
74
|
+
* Format a comma-separated list of example anchors with an optional line-number
|
|
75
|
+
* prefix, quoted for inclusion in error messages: `"160", "42", "7"`.
|
|
76
|
+
*/
|
|
77
|
+
export declare function describeAnchorExamples(linePrefix?: string): string;
|
|
78
|
+
/** Format a hashline section header for a file path and snapshot tag. */
|
|
79
|
+
export declare function formatHashlineHeader(filePath: string, fileHash: string): string;
|
|
80
|
+
/** Formats a single numbered line as `LINE:TEXT`. */
|
|
81
|
+
export declare function formatNumberedLine(lineNumber: number, line: string): string;
|
|
82
|
+
/** Format file text with hashline-mode line-number prefixes for display. */
|
|
83
|
+
export declare function formatNumberedLines(text: string, startLine?: number): string;
|