@oh-my-pi/hashline 15.12.3 → 15.13.0

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,121 +2,81 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
- ## [15.12.0] - 2026-06-12
6
-
7
- ### Changed
8
-
9
- - Condensed all parser/applier/patcher error and warning messages: shorter wording, same diagnostic anchors (op names, line numbers, suggested fallback forms)
10
-
11
- ## [15.11.4] - 2026-06-12
12
-
13
- ### Added
14
-
15
- - 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
16
- - 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`
17
-
18
- ### Changed
19
-
20
- - 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
21
-
22
- ## [15.11.1] - 2026-06-11
23
-
24
- ### Fixed
25
-
26
- - 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))
27
-
28
- ## [15.11.0] - 2026-06-10
29
-
30
- ### Changed
31
-
32
- - 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
33
-
34
- ## [15.10.11] - 2026-06-10
35
-
36
5
  ### Breaking Changes
37
6
 
38
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.
39
9
 
40
10
  ### Added
41
11
 
12
+ - 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
+ - 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`
42
14
  - 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
43
15
  - 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
44
16
  - 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
45
22
 
46
23
  ### Changed
47
24
 
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
48
28
  - 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
49
29
  - 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.
50
33
 
51
34
  ### Fixed
52
35
 
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))
53
39
  - 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
54
40
  - 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
55
41
  - 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
56
42
  - 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)
57
-
58
- ## [15.10.5] - 2026-06-08
59
-
60
- ### Added
61
-
62
- - 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
63
-
64
- ### Changed
65
-
66
- - Changed `buildCompactDiffPreview` to omit removed lines from the preview while preserving removal counts for offset tracking
67
- - 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)
68
-
69
- ### Fixed
70
-
71
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.
72
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)).
73
49
 
74
- ## [15.10.3] - 2026-06-08
75
-
76
- ### Added
77
-
78
- - 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.
50
+ ## [15.13.0] - 2026-06-14
79
51
 
80
- ### Changed
52
+ ## [15.12.5] - 2026-06-13
81
53
 
82
- - 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.
54
+ ## [15.12.0] - 2026-06-12
83
55
 
84
- ## [15.10.2] - 2026-06-08
56
+ ## [15.11.4] - 2026-06-12
85
57
 
86
- ### Fixed
58
+ ## [15.11.1] - 2026-06-11
87
59
 
88
- - 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)).
60
+ ## [15.11.0] - 2026-06-10
89
61
 
90
- ## [15.9.67] - 2026-06-06
62
+ ## [15.10.11] - 2026-06-10
91
63
 
92
- ### Breaking Changes
64
+ ## [15.10.5] - 2026-06-08
93
65
 
94
- - Changed hashline file section headers from `¶PATH#TAG` to `[PATH#TAG]` so model-authored edits use ASCII delimiters instead of a pilcrow sigil.
66
+ ## [15.10.3] - 2026-06-08
95
67
 
96
- ### Fixed
68
+ ## [15.10.2] - 2026-06-08
97
69
 
98
- - Fixed missing-header diagnostics and copied-content prefix stripping to consistently teach and recognize 4-hex snapshot tags.
70
+ ## [15.9.67] - 2026-06-06
99
71
 
100
72
  ## [15.8.2] - 2026-06-03
101
73
 
102
- ### Fixed
103
-
104
- - 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.
105
-
106
74
  ## [15.8.0] - 2026-06-02
107
75
 
108
- ### Fixed
109
-
110
- - 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)).
111
-
112
76
  ## [15.7.0] - 2026-05-31
113
- ### Added
114
-
115
- - 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
116
- - Added `BlockResolver` support in `Patcher` and `PatchSection.applyTo`/`applyPartialTo` to wire language-specific block-resolution at apply time
117
- - Added `resolveBlockEdits` and block edit type definitions to the package API for resolving deferred `replace block` / `delete block` edits
118
77
 
119
78
  ## [15.5.13] - 2026-05-29
79
+
120
80
  ### Breaking Changes
121
81
 
122
82
  - Changed hashline section tags from 3-hex to 4-hex content-hash tags, so legacy 3-digit tags are no longer valid
@@ -152,6 +112,7 @@
152
112
  - `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.
153
113
 
154
114
  ## [15.5.8] - 2026-05-28
115
+
155
116
  ### Breaking Changes
156
117
 
157
118
  - 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.
@@ -210,6 +171,7 @@
210
171
  All notable changes to this package will be documented in this file.
211
172
 
212
173
  ## [15.5.4] - 2026-05-27
174
+
213
175
  ### Added
214
176
 
215
177
  - Added a high-level `Patcher` API with all-or-nothing `apply` and staged `prepare`/`commit` flows for multi-file patch updates
@@ -225,4 +187,4 @@ All notable changes to this package will be documented in this file.
225
187
 
226
188
  - Fixed repeated patch application mutating cached `after_anchor` edits between target snapshots
227
189
  - Fixed multi-section patching to preflight write policies and reject duplicate canonical targets before any section is committed
228
- - Fixed mixed line-ending restoration to preserve the first newline style instead of rewriting ties to LF
190
+ - Fixed mixed line-ending restoration to preserve the first newline style instead of rewriting ties to LF
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/hashline",
4
- "version": "15.12.3",
4
+ "version": "15.13.0",
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",
package/src/apply.ts CHANGED
@@ -318,11 +318,22 @@ function findDuplicatePrefix(group: ReplacementGroup, fileLines: readonly string
318
318
  return 0;
319
319
  }
320
320
 
321
+ function payloadEndsWithDeletedSuffix(group: ReplacementGroup, fileLines: readonly string[], count: number): boolean {
322
+ if (group.payload.length < count) return false;
323
+ const deletedStart = group.endLine - count;
324
+ const payloadStart = group.payload.length - count;
325
+ for (let offset = 0; offset < count; offset++) {
326
+ if (group.payload[payloadStart + offset] !== fileLines[deletedStart + offset]) return false;
327
+ }
328
+ return true;
329
+ }
330
+
321
331
  /**
322
332
  * Smallest `m` such that the range's last `m` deleted lines are all pure
323
- * structural closers and sparing them (keeping instead of deleting) zeroes
324
- * `delta`. The mirror mistake: a range that swallows a closing delimiter the
325
- * payload never restates.
333
+ * structural closers, the payload does not already restate those same suffix
334
+ * lines, and sparing them (keeping instead of deleting) zeroes `delta`. The
335
+ * mirror mistake: a range that swallows a closing delimiter the payload never
336
+ * restates.
326
337
  */
327
338
  function findDroppedSuffixClosers(
328
339
  group: ReplacementGroup,
@@ -333,6 +344,7 @@ function findDroppedSuffixClosers(
333
344
  const maxM = group.deleteIndices.length;
334
345
  for (let m = 1; m <= maxM; m++) {
335
346
  if (!STRUCTURAL_CLOSER_RE.test(fileLines[group.endLine - m] ?? "")) break;
347
+ if (payloadEndsWithDeletedSuffix(group, fileLines, m)) continue;
336
348
  if (balanceEqual(computeDelimiterBalance(fileLines.slice(group.endLine - m, group.endLine)), wanted)) return m;
337
349
  }
338
350
  return 0;
package/src/input.ts CHANGED
@@ -88,8 +88,9 @@ function normalizeHashlinePath(rawPath: string, cwd?: string): string {
88
88
  const unquoted = stripApplyPatchPathNoise(unquoteHashlinePath(rawPath.trim()));
89
89
  if (!cwd || !path.isAbsolute(unquoted)) return unquoted;
90
90
  const relative = path.relative(path.resolve(cwd), path.resolve(unquoted));
91
+ const normalizedRelative = relative.split(path.sep).join("/");
91
92
  const isWithinCwd = relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
92
- return isWithinCwd ? relative || "." : unquoted;
93
+ return isWithinCwd ? normalizedRelative || "." : unquoted;
93
94
  }
94
95
 
95
96
  interface RawSection {