@oh-my-pi/hashline 15.13.0 → 15.13.2

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 CHANGED
@@ -2,79 +2,156 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [15.13.2] - 2026-06-15
6
+
5
7
  ### Breaking Changes
6
8
 
7
- - Changed `BlockResolution.isDelete` to `BlockResolution.op` (`"replace" | "delete" | "insert_after"`) so resolutions can describe every block-anchored op
8
- - Changed hashline file section headers from `¶PATH#TAG` to `[PATH#TAG]` so model-authored edits use ASCII delimiters instead of a pilcrow sigil.
9
+ - Renamed all hashline DSL operators to concise abbreviated keywords:
10
+ - `replace` -> `SWAP`
11
+ - `delete` -> `DEL`
12
+ - `insert before`/`after`/`head`/`tail` -> `INS.PRE`/`POST`/`HEAD`/`TAIL`
13
+ - `replace_block` -> `SWAP.BLK`
14
+ - `delete_block` -> `DEL.BLK`
15
+ - `insert_after_block` -> `INS.BLK.POST`
16
+
17
+ ## [15.13.1] - 2026-06-15
18
+
19
+ ### Breaking Changes
20
+
21
+ - Rejected edits anchored to lines not displayed in the tagged read/search output, requiring unseen ranges to be re-read before reapplying
22
+
23
+ ### Changed
24
+
25
+ - 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
26
+
27
+ ### Fixed
28
+
29
+ - Auto-repaired one-sided multi-line boundary echoes by dropping delimiter-neutral duplicated boundary lines and emitted a boundary-echo warning
30
+ - Normalized cwd-relative hashline paths to forward-slash form on Windows.
31
+ - Parser now treats a leading `\` on inline payload bodies as the payload delimiter, matching standalone payload rows.
32
+ - Restored the warning emitted when escaped indented payload rows (`\\ TEXT`) are accepted as payload delimiters.
33
+
34
+ ## [15.12.5] - 2026-06-13
35
+
36
+ ### Fixed
37
+
38
+ - Fixed delimiter-balance boundary repair so it does not keep a deleted structural closer when the replacement payload already restates that closer.
39
+
40
+ ## [15.12.0] - 2026-06-12
41
+
42
+ ### Changed
43
+
44
+ - Condensed all parser/applier/patcher error and warning messages: shorter wording, same diagnostic anchors (op names, line numbers, suggested fallback forms)
45
+
46
+ ## [15.11.4] - 2026-06-12
9
47
 
10
48
  ### Added
11
49
 
12
50
  - 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
13
51
  - 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`
52
+
53
+ ### Changed
54
+
55
+ - 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
56
+
57
+ ## [15.11.1] - 2026-06-11
58
+
59
+ ### Fixed
60
+
61
+ - 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/can1357/oh-my-pi/issues/2292))
62
+
63
+ ## [15.11.0] - 2026-06-10
64
+
65
+ ### Changed
66
+
67
+ - 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
68
+
69
+ ## [15.10.11] - 2026-06-10
70
+
71
+ ### Breaking Changes
72
+
73
+ - Changed `BlockResolution.isDelete` to `BlockResolution.op` (`"replace" | "delete" | "insert_after"`) so resolutions can describe every block-anchored op
74
+
75
+ ### Added
76
+
14
77
  - 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
15
78
  - 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
16
79
  - 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
17
- - 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
18
- - 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.
19
- - 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
20
- - Added `BlockResolver` support in `Patcher` and `PatchSection.applyTo`/`applyPartialTo` to wire language-specific block-resolution at apply time
21
- - Added `resolveBlockEdits` and block edit type definitions to the package API for resolving deferred `replace block` / `delete block` edits
22
80
 
23
81
  ### Changed
24
82
 
25
- - Condensed all parser/applier/patcher error and warning messages: shorter wording, same diagnostic anchors (op names, line numbers, suggested fallback forms)
26
- - 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
27
- - 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
28
83
  - 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
29
84
  - 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
30
- - Changed `buildCompactDiffPreview` to omit removed lines from the preview while preserving removal counts for offset tracking
31
- - 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)
32
- - 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.
33
85
 
34
86
  ### Fixed
35
87
 
36
- - Normalized cwd-relative hashline paths to forward-slash form on Windows.
37
- - Fixed delimiter-balance boundary repair so it does not keep a deleted structural closer when the replacement payload already restates that closer.
38
- - 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/can1357/oh-my-pi/issues/2292))
39
88
  - 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
40
89
  - 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
41
90
  - 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
42
91
  - 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)
43
- - 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.
44
- - Fixed compact edit previews to render added/current lines without diff-prefix padding and normalize adjacent ASCII/Unicode elision markers to one `…`.
45
- - 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/can1357/oh-my-pi/issues/1492)).
46
- - Fixed missing-header diagnostics and copied-content prefix stripping to consistently teach and recognize 4-hex snapshot tags.
47
- - 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.
48
- - 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/can1357/oh-my-pi/issues/1664)).
49
-
50
- ## [15.13.0] - 2026-06-14
51
92
 
52
- ## [15.12.5] - 2026-06-13
93
+ ## [15.10.5] - 2026-06-08
53
94
 
54
- ## [15.12.0] - 2026-06-12
95
+ ### Added
55
96
 
56
- ## [15.11.4] - 2026-06-12
97
+ - 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
57
98
 
58
- ## [15.11.1] - 2026-06-11
99
+ ### Changed
59
100
 
60
- ## [15.11.0] - 2026-06-10
101
+ - Changed `buildCompactDiffPreview` to omit removed lines from the preview while preserving removal counts for offset tracking
102
+ - 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)
61
103
 
62
- ## [15.10.11] - 2026-06-10
104
+ ### Fixed
63
105
 
64
- ## [15.10.5] - 2026-06-08
106
+ - 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.
107
+ - Fixed compact edit previews to render added/current lines without diff-prefix padding and normalize adjacent ASCII/Unicode elision markers to one `…`.
65
108
 
66
109
  ## [15.10.3] - 2026-06-08
67
110
 
111
+ ### Added
112
+
113
+ - 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.
114
+
115
+ ### Changed
116
+
117
+ - 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.
118
+
68
119
  ## [15.10.2] - 2026-06-08
69
120
 
121
+ ### Fixed
122
+
123
+ - 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/can1357/oh-my-pi/issues/1492)).
124
+
70
125
  ## [15.9.67] - 2026-06-06
71
126
 
127
+ ### Breaking Changes
128
+
129
+ - Changed hashline file section headers from `¶PATH#TAG` to `[PATH#TAG]` so model-authored edits use ASCII delimiters instead of a pilcrow sigil.
130
+
131
+ ### Fixed
132
+
133
+ - Fixed missing-header diagnostics and copied-content prefix stripping to consistently teach and recognize 4-hex snapshot tags.
134
+
72
135
  ## [15.8.2] - 2026-06-03
73
136
 
137
+ ### Fixed
138
+
139
+ - 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.
140
+
74
141
  ## [15.8.0] - 2026-06-02
75
142
 
143
+ ### Fixed
144
+
145
+ - 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/can1357/oh-my-pi/issues/1664)).
146
+
76
147
  ## [15.7.0] - 2026-05-31
77
148
 
149
+ ### Added
150
+
151
+ - 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
152
+ - Added `BlockResolver` support in `Patcher` and `PatchSection.applyTo`/`applyPartialTo` to wire language-specific block-resolution at apply time
153
+ - Added `resolveBlockEdits` and block edit type definitions to the package API for resolving deferred `replace block` / `delete block` edits
154
+
78
155
  ## [15.5.13] - 2026-05-29
79
156
 
80
157
  ### Breaking Changes
@@ -85,7 +162,7 @@
85
162
  ### Added
86
163
 
87
164
  - Added `maxPaths` and `maxVersionsPerPath` options to `InMemorySnapshotStore` to bound tracked paths and per-path snapshot history
88
- - 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 couples are also bounded by line count: structural balance delta repair is capped to 10 duplicate lines across all channels combined, massive balanced blocks are skipped.
165
+ - 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`.
89
166
 
90
167
  ### Changed
91
168
 
@@ -153,7 +230,7 @@
153
230
 
154
231
  ### Breaking Changes
155
232
 
156
- - Redesigned hashline syntax around range anchors (`A-B:`, `A:`, `BOF:`, `EOF:`) and per-line payload sigils (`|`, `↑`, `↓`). Old op-line insert syntax and `\` payload continuations are no longer supported.
233
+ - Changed hashline payload continuations from `+TEXT` to `\TEXT`; use `\` for an explicit blank payload line.
157
234
 
158
235
  ### Added
159
236
 
@@ -166,7 +243,7 @@
166
243
 
167
244
  ### Removed
168
245
 
169
- - Removed legacy deletion semantics that treated bare `A-B:` as a blank-line replacement; a bare range anchor now deletes the range.
246
+ - Removed the `A-B!` / `A!` deletion operator. Use `A-B:` with the desired payload (or empty payload to blank the range) instead.
170
247
 
171
248
  All notable changes to this package will be documented in this file.
172
249
 
package/README.md CHANGED
@@ -26,7 +26,7 @@ await fs.writeText("hello.ts", before);
26
26
  const tag = snapshots.record("hello.ts", before);
27
27
  const patcher = new Patcher({ fs, snapshots });
28
28
  const patch = Patch.parse(String.raw`[hello.ts#${tag}]
29
- replace 1..1:
29
+ SWAP 1..1:
30
30
  +const greeting = "hello";`);
31
31
  const result = await patcher.apply(patch);
32
32
 
@@ -47,11 +47,11 @@ still matches the recorded content hash, and refusing or attempting
47
47
  session-aware recovery on mismatch.
48
48
 
49
49
  Inside a section:
50
- - `replace A..B:` — replace lines A..B with following `+TEXT` body rows.
51
- - `replace block A:` — replace the syntactic block beginning on line A.
52
- - `delete A..B` / `delete block A` — delete concrete lines or a resolved block.
53
- - `insert before A:` / `insert after A:` / `insert head:` / `insert tail:` — insert following body rows.
54
- - `insert after block A:` — insert following body rows after the resolved block's last line.
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
55
  - `+TEXT` — literal body row (use `+` alone for a blank line).
56
56
 
57
57
  ## Abstractions
@@ -6,7 +6,7 @@ export interface ResolveBlockEditsOptions {
6
6
  * `blockUnresolvedMessage` error — used by the authoritative apply + final
7
7
  * preview paths. `"drop"` silently skips the edit — used by the streaming
8
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
9
+ * throw. Unresolvable `insert_after_block N:` edits never reach this: they
10
10
  * are lowered to plain `insert after N:` with a warning.
11
11
  */
12
12
  onUnresolved?: "throw" | "drop";
@@ -19,7 +19,7 @@ export interface ResolveBlockEditsOptions {
19
19
  onResolved?: (resolution: BlockResolution) => void;
20
20
  /**
21
21
  * Invoked once per diagnostic produced while resolving — currently the
22
- * `insert after block N:` lowerings (closer anchor or unresolvable block).
22
+ * `insert_after_block N:` lowerings (closer anchor or unresolvable block).
23
23
  * Hosts should surface these on the apply result's `warnings`.
24
24
  */
25
25
  onWarning?: (message: string) => void;
@@ -10,22 +10,25 @@ export declare const HL_FILE_SUFFIX = "]";
10
10
  /** Payload sigil for literal body rows. */
11
11
  export declare const HL_PAYLOAD_REPLACE = "+";
12
12
  /** Hunk-header keyword for concrete line replacement. */
13
- export declare const HL_REPLACE_KEYWORD = "replace";
14
- /** Hunk-header sub-keyword: `replace block N:` resolves N to a tree-sitter block range. */
15
- export declare const HL_BLOCK_KEYWORD = "block";
13
+ export declare const HL_REPLACE_KEYWORD = "SWAP";
16
14
  /** Hunk-header keyword for concrete line deletion. */
17
- export declare const HL_DELETE_KEYWORD = "delete";
15
+ export declare const HL_DELETE_KEYWORD = "DEL";
18
16
  /** Hunk-header keyword for insertion operations. */
19
- export declare const HL_INSERT_KEYWORD = "insert";
17
+ export declare const HL_INSERT_KEYWORD = "INS";
20
18
  /** Insert position keyword for inserting before a concrete line. */
21
- export declare const HL_INSERT_BEFORE = "before";
19
+ export declare const HL_INSERT_BEFORE = "PRE";
22
20
  /** Insert position keyword for inserting after a concrete line. */
23
- export declare const HL_INSERT_AFTER = "after";
21
+ export declare const HL_INSERT_AFTER = "POST";
24
22
  /** Insert position keyword for inserting at the start of the file. */
25
- export declare const HL_INSERT_HEAD = "head";
23
+ export declare const HL_INSERT_HEAD = "HEAD";
26
24
  /** Insert position keyword for inserting at the end of the file. */
27
- export declare const HL_INSERT_TAIL = "tail";
28
- /** Hunk-header terminator for body-bearing operations. */
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";
29
32
  export declare const HL_HEADER_COLON = ":";
30
33
  /** Separator between a hashline file path and its opaque snapshot tag. */
31
34
  export declare const HL_FILE_HASH_SEP = "#";
@@ -50,7 +50,7 @@ export declare class PatchSection {
50
50
  * method directly when you've already validated the file content and
51
51
  * just want the result.
52
52
  *
53
- * `blockResolver` resolves any `replace block N:` edits against `text`; an
53
+ * `blockResolver` resolves any `replace_block N:` edits against `text`; an
54
54
  * unresolvable block throws (this is the final, authoritative preview path).
55
55
  */
56
56
  applyTo(text: string, blockResolver?: BlockResolver): ApplyResult;
@@ -61,7 +61,7 @@ export declare class PatchSection {
61
61
  * empty-payload edit. Intended for incremental diff previews; the writer
62
62
  * path should always use {@link applyTo}.
63
63
  *
64
- * `blockResolver` resolves any `replace block N:` edits against `text`; an
64
+ * `blockResolver` resolves any `replace_block N:` edits against `text`; an
65
65
  * unresolvable block is silently dropped so a half-written file does not
66
66
  * throw mid-stream.
67
67
  */
@@ -17,51 +17,49 @@ export declare const END_PATCH_MARKER = "*** End Patch";
17
17
  */
18
18
  export declare const ABORT_MARKER = "*** Abort";
19
19
  /** Two consecutive hunks targeted the exact same concrete range. */
20
- export declare const REPLACE_PAIR_COALESCED_WARNING = "Two hunks targeted the same range; kept only the second. One `replace N..M:` hunk per range \u2014 the body is the final content, never old+new.";
21
- /** Bare bodyless hunk followed by an overlapping concrete hunk. */
22
- export declare const REPLACE_PAIR_COALESCED_OVERLAP_WARNING = "Dropped a bare hunk overlapped by the concrete hunk after it. One `replace N..M:` hunk per range \u2014 the body is the final content, never old+new.";
20
+ export declare const REPLACE_PAIR_COALESCED_WARNING = "Two hunks targeted the same range; kept only the second. One `SWAP N..M:` hunk per range \u2014 the body is the final content, never old+new.";
23
21
  /** Bare body rows auto-converted to literal `+` rows. */
24
22
  export declare const BARE_BODY_AUTO_PIPED_WARNING = "Auto-prefixed bare body row(s) with `+`. Body rows must be `+TEXT` literal lines.";
25
23
  /** Unified-diff-style `-` row in a hunk body. */
26
24
  export declare const MINUS_ROW_REJECTED = "`-` rows are not valid; the range already names the lines being changed. For a literal `-` line, write `+-\u2026`.";
27
25
  /** Replace hunk with no body. */
28
- export declare const EMPTY_REPLACE = "`replace N..M:` needs at least one `+TEXT` body row. To delete lines, use `delete N..M`.";
29
- /** `replace block N:` hunk with no body. */
30
- export declare const EMPTY_BLOCK = "`replace block N:` needs at least one `+TEXT` body row. To delete a block, use `delete block N`.";
26
+ export declare const EMPTY_REPLACE = "`SWAP N..M:` needs at least one `+TEXT` body row. To delete lines, use `DEL N..M`.";
27
+ /** `replace_block N:` hunk with no body. */
28
+ export declare const EMPTY_BLOCK = "`SWAP.BLK N:` needs at least one `+TEXT` body row. To delete a block, use `DEL.BLK N`.";
31
29
  /**
32
30
  * Block-anchored replace/delete could not resolve to a syntactic block
33
31
  * (unsupported language, blank/out-of-range line, no node beginning on N, or
34
32
  * parse error). Appends a {@link formatAnchoredContext} preview when
35
- * `fileLines` is given. `insert after block N:` never reaches this — it is
33
+ * `fileLines` is given. `insert_after_block N:` never reaches this — it is
36
34
  * lowered to plain `insert after N:` instead (see
37
35
  * {@link insertAfterBlockUnresolvedLoweredWarning}).
38
36
  */
39
37
  export declare function blockUnresolvedMessage(line: number, op?: "replace" | "delete", fileLines?: readonly string[]): string;
40
38
  /** Block-anchored edit reached a path with no {@link BlockResolver} wired in — a host-configuration bug. */
41
- export declare const BLOCK_RESOLVER_UNAVAILABLE = "`replace block`/`delete block`/`insert after block` are not available here (no block resolver configured). Use a concrete line range.";
39
+ export declare const BLOCK_RESOLVER_UNAVAILABLE = "`SWAP.BLK`/`DEL.BLK`/`INS.BLK.POST` are not available here (no block resolver configured). Use a concrete line range.";
42
40
  /**
43
- * `insert after block N:` anchored on a closing-delimiter line, lowered to
41
+ * `insert_after_block N:` anchored on a closing-delimiter line, lowered to
44
42
  * plain `insert after N:` — the closer ends a block, and inserting after it
45
43
  * is exactly what the plain form does.
46
44
  */
47
45
  export declare function insertAfterBlockCloserLoweredWarning(line: number): string;
48
46
  /**
49
- * `insert after block N:` anchor unresolvable (unsupported language, blank
47
+ * `insert_after_block N:` anchor unresolvable (unsupported language, blank
50
48
  * line, parse error, or no resolver), lowered to plain `insert after N:` —
51
49
  * applying with a warning beats failing the patch.
52
50
  */
53
51
  export declare function insertAfterBlockUnresolvedLoweredWarning(line: number): string;
54
52
  /**
55
- * Internal invariant: `applyEdits` received an unresolved `replace block N:`
53
+ * Internal invariant: `applyEdits` received an unresolved `replace_block N:`
56
54
  * edit; `resolveBlockEdits` must run first. Wiring bug, not authored input.
57
55
  */
58
- export declare const UNRESOLVED_BLOCK_INTERNAL = "internal error: unresolved `replace block` edit reached the applier (resolveBlockEdits was not run).";
56
+ export declare const UNRESOLVED_BLOCK_INTERNAL = "internal error: unresolved `SWAP.BLK` edit reached the applier (resolveBlockEdits was not run).";
59
57
  /** Delete hunk received a body row. */
60
- export declare const DELETE_TAKES_NO_BODY = "`delete N..M` does not take body rows. Remove the body, or use `replace N..M:`.";
61
- /** `delete block N` hunk received a body row. */
62
- export declare const DELETE_BLOCK_TAKES_NO_BODY = "`delete block N` does not take body rows. Remove the body, or use `replace block N:`.";
58
+ export declare const DELETE_TAKES_NO_BODY = "`DEL N..M` does not take body rows. Remove the body, or use `SWAP N..M:`.";
59
+ /** `delete_block N` hunk received a body row. */
60
+ export declare const DELETE_BLOCK_TAKES_NO_BODY = "`DEL.BLK N` does not take body rows. Remove the body, or use `SWAP.BLK N:`.";
63
61
  /** Insert hunk with no body. */
64
- export declare const EMPTY_INSERT = "`insert` needs at least one `+TEXT` body row.";
62
+ export declare const EMPTY_INSERT = "`INS` needs at least one `+TEXT` body row.";
65
63
  /**
66
64
  * `insert after` body indented shallower than the anchor: the landing slid
67
65
  * forward past trailing closer lines — the common "anchored on the last line
@@ -69,7 +67,7 @@ export declare const EMPTY_INSERT = "`insert` needs at least one `+TEXT` body ro
69
67
  */
70
68
  export declare function afterInsertLandingShiftWarning(anchorLine: number, landingLine: number, crossed: number): string;
71
69
  /**
72
- * `insert after block N:` body indented deeper than the block's closer: the
70
+ * `insert_after_block N:` body indented deeper than the block's closer: the
73
71
  * landing was pulled inside the block — a deeper body almost always means
74
72
  * "append inside the block's body".
75
73
  */
@@ -90,9 +88,26 @@ export declare const RECOVERY_SESSION_REPLAY_WARNING = "Recovered by replaying y
90
88
  * Head/tail position is content-independent, so drift is non-fatal: apply
91
89
  * onto live content and warn instead of hard-failing.
92
90
  */
93
- export declare const HEADTAIL_DRIFT_WARNING = "Applied the `insert head:`/`insert tail:` edit despite a stale snapshot tag (file changed since your read) \u2014 head/tail position is content-independent. Re-read if the drift was unexpected.";
91
+ export declare const HEADTAIL_DRIFT_WARNING = "Applied the `INS.HEAD:`/`INS.TAIL:` edit despite a stale snapshot tag (file changed since your read) \u2014 head/tail position is content-independent. Re-read if the drift was unexpected.";
94
92
  /**
95
93
  * Section omitted the mandatory snapshot tag. Shared by the apply
96
94
  * ({@link Patcher.prepare}) and preview/diff paths so both stay in lockstep.
97
95
  */
98
96
  export declare function missingSnapshotTagMessage(sectionPath: string): string;
97
+ /**
98
+ * An anchored edit referenced lines the read that minted the cited tag never
99
+ * displayed (a partial range, or a structural summary that collapsed bodies).
100
+ * Editing lines you have not read is the off-by-memory failure that mangles
101
+ * files; reject and make the model re-read those exact lines first.
102
+ */
103
+ export declare function unseenLinesMessage(sectionPath: string, unseenLines: readonly number[], tag: string): string;
104
+ /** Op kind of a deferred block edit, for {@link blockSingleLineMessage}. */
105
+ export type BlockOp = "replace" | "delete" | "insert_after";
106
+ /**
107
+ * A `replace_block`/`delete_block`/`insert_after_block` anchor resolved to a
108
+ * single line — almost always a bare statement the model mis-anchored, not a
109
+ * multi-line construct. The plain op is unambiguous for one line; the block
110
+ * form only earns its keep when it spares counting a closing line you cannot
111
+ * see. Reject and point at both fixes.
112
+ */
113
+ export declare function blockSingleLineMessage(line: number, op: BlockOp): string;
@@ -10,8 +10,8 @@ export interface PatcherOptions {
10
10
  /** Snapshot store that minted and resolves hashline section tags. Required. */
11
11
  snapshots: SnapshotStore;
12
12
  /**
13
- * Resolves `replace block N:` anchors to concrete line spans via tree-sitter.
14
- * Optional: when omitted, any `replace block N:` edit throws on apply (the
13
+ * Resolves `replace_block N:` anchors to concrete line spans via tree-sitter.
14
+ * Optional: when omitted, any `replace_block N:` edit throws on apply (the
15
15
  * host did not wire a resolver). Plain line-range ops never need it.
16
16
  */
17
17
  blockResolver?: BlockResolver;
@@ -41,7 +41,7 @@ export interface PatchSectionResult {
41
41
  /** Warnings collected by the parser, applier, and (optionally) recovery. */
42
42
  warnings: string[];
43
43
  /**
44
- * Resolved spans for any `replace block`/`delete block` ops, present when the
44
+ * Resolved spans for any `replace_block`/`delete_block` ops, present when the
45
45
  * apply matched the tagged content. Undefined for patches with no block ops
46
46
  * (and for resolutions routed through drift recovery, where numbers shift).
47
47
  */
@@ -11,6 +11,15 @@ export interface Snapshot {
11
11
  readonly hash: string;
12
12
  /** Timestamp (ms since epoch) the version was recorded. */
13
13
  recordedAt: number;
14
+ /**
15
+ * 1-indexed file lines a producer (read/search) actually *displayed* under
16
+ * this tag. A partial read (range, or a structural summary that collapsed
17
+ * bodies) leaves this sparse; a whole-file read fills every line. Multiple
18
+ * reads of the same content union into one set. `undefined` means "no
19
+ * provenance recorded" — the patcher then skips the seen-line check and
20
+ * applies as before. Mutated in place as more of the same content is read.
21
+ */
22
+ seenLines?: Set<number>;
14
23
  }
15
24
  /**
16
25
  * Storage seam for full-file version snapshots. The patcher calls {@link head}
@@ -22,8 +31,19 @@ export declare abstract class SnapshotStore {
22
31
  abstract head(path: string): Snapshot | null;
23
32
  /** Recorded version for `path` whose tag equals `hash`, or `null`. */
24
33
  abstract byHash(path: string, hash: string): Snapshot | null;
25
- /** Record the full normalized text of `path` and return its content tag. */
26
- abstract record(path: string, fullText: string): string;
34
+ /**
35
+ * Record the full normalized text of `path` and return its content tag.
36
+ * `seenLines` (optional) are the 1-indexed lines the producer displayed;
37
+ * they merge into {@link Snapshot.seenLines} across reads of identical text.
38
+ */
39
+ abstract record(path: string, fullText: string, seenLines?: Iterable<number>): string;
40
+ /**
41
+ * Merge `lines` into the {@link Snapshot.seenLines} of the version whose tag
42
+ * equals `hash`. No-op when no such version is retained (the content aged
43
+ * out or was overwritten). Lets producers attach displayed lines after the
44
+ * tag was already minted (the body is formatted after the hash is computed).
45
+ */
46
+ abstract recordSeenLines(path: string, hash: string, lines: Iterable<number>): void;
27
47
  /** Drop the version history for a single path. */
28
48
  abstract invalidate(path: string): void;
29
49
  /** Drop every version history. */
@@ -55,7 +75,8 @@ export declare class InMemorySnapshotStore extends SnapshotStore {
55
75
  constructor(options?: InMemorySnapshotStoreOptions);
56
76
  head(path: string): Snapshot | null;
57
77
  byHash(path: string, hash: string): Snapshot | null;
58
- record(path: string, fullText: string): string;
78
+ record(path: string, fullText: string, seenLines?: Iterable<number>): string;
79
+ recordSeenLines(path: string, hash: string, lines: Iterable<number>): void;
59
80
  invalidate(path: string): void;
60
81
  clear(): void;
61
82
  }
@@ -34,7 +34,7 @@ export type Edit = {
34
34
  index: number;
35
35
  mode?: "replacement";
36
36
  /**
37
- * Present on inserts lowered from `insert after block N:`: the
37
+ * Present on inserts lowered from `insert_after_block N:`: the
38
38
  * resolved block's first line. Lets the applier slide a body that
39
39
  * claims a depth inside the block back across the block's trailing
40
40
  * closer lines (never above this line).
@@ -48,13 +48,13 @@ export type Edit = {
48
48
  oldAssertion?: string;
49
49
  } | {
50
50
  /**
51
- * Deferred block edit (`replace block N:` / `delete block N` /
52
- * `insert after block N:`). The exact line span is unknown at parse
51
+ * Deferred block edit (`replace_block N:` / `delete_block N` /
52
+ * `insert_after_block N:`). The exact line span is unknown at parse
53
53
  * time — it is computed by {@link resolveBlockEdits} once file text +
54
54
  * path (→ language) are available, then expanded into concrete edits:
55
- * a non-empty `payloads` without `mode` (from `replace block`) becomes
55
+ * a non-empty `payloads` without `mode` (from `replace_block`) becomes
56
56
  * the same `replacement` inserts + deletes that `replace start..end:`
57
- * produces; an empty `payloads` (from `delete block`) becomes a pure
57
+ * produces; an empty `payloads` (from `delete_block`) becomes a pure
58
58
  * range deletion; `mode: "insert_after"` becomes plain `after_anchor`
59
59
  * inserts at the block's last line. `applyEdits` never sees this
60
60
  * variant.
@@ -75,7 +75,7 @@ export interface ApplyResult {
75
75
  /** Diagnostic warnings collected by the parser, patcher, or recovery. */
76
76
  warnings?: string[];
77
77
  /**
78
- * Resolved spans for each `replace block`/`delete block` op in this apply,
78
+ * Resolved spans for each `replace_block`/`delete_block` op in this apply,
79
79
  * in patch order. Present only when the apply matched the tagged content
80
80
  * (the common no-drift path), so the line numbers line up with what the
81
81
  * caller read. Absent when there were no block ops.
@@ -121,7 +121,7 @@ export interface CompactDiffOptions {
121
121
  maxUnchangedRun?: number;
122
122
  }
123
123
  /**
124
- * Resolved 1-indexed inclusive line span of a `replace block N:` target.
124
+ * Resolved 1-indexed inclusive line span of a `replace_block N:` target.
125
125
  */
126
126
  export interface BlockSpan {
127
127
  /** First line of the block (1-indexed, inclusive). */
@@ -130,7 +130,7 @@ export interface BlockSpan {
130
130
  end: number;
131
131
  }
132
132
  /**
133
- * One `replace block N:` / `delete block N` / `insert after block N:` anchor
133
+ * One `replace_block N:` / `delete_block N` / `insert_after_block N:` anchor
134
134
  * resolved to its concrete line span. Surfaced on {@link ApplyResult} so the
135
135
  * host can echo "block N → lines start..end" and let the model catch a wrong
136
136
  * opener — e.g. a decorator or doc-comment that sits in a separate node
@@ -146,7 +146,7 @@ export interface BlockResolution {
146
146
  /** Which block op produced this resolution. */
147
147
  op: "replace" | "delete" | "insert_after";
148
148
  }
149
- /** Request handed to a {@link BlockResolver} to resolve one `replace block N:` anchor. */
149
+ /** Request handed to a {@link BlockResolver} to resolve one `replace_block N:` anchor. */
150
150
  export interface BlockResolverRequest {
151
151
  /** Target file path (used to infer language by extension). */
152
152
  path: string;
@@ -156,7 +156,7 @@ export interface BlockResolverRequest {
156
156
  line: number;
157
157
  }
158
158
  /**
159
- * Resolves a `replace block N:` anchor to the line span of the syntactic block
159
+ * Resolves a `replace_block N:` anchor to the line span of the syntactic block
160
160
  * that begins on line N. Returns `null` when no block can be resolved
161
161
  * (unrecognized language, blank/out-of-range line, no node begins there, or the
162
162
  * resolved subtree has a syntax error). Pure seam: the hashline core declares
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/hashline",
4
- "version": "15.13.0",
4
+ "version": "15.13.2",
5
5
  "description": "Hashline: a compact, line-anchored patch language and applier. Pluggable FS/IO so it works over disk, in-memory, or any custom backend.",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",