@oh-my-pi/hashline 17.3.2 → 17.3.3

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,6 +2,12 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [17.3.3] - 2026-08-14
6
+
7
+ ### Fixed
8
+
9
+ - Recovered dangling range separators in hunk headers (`PUT 244.=:`, `CUT 5.=`) as single-line ranges (`N.=N`) instead of rejecting the header as an orphan payload line.
10
+
5
11
  ## [17.3.0] - 2026-08-13
6
12
 
7
13
  ### Fixed
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/hashline",
4
- "version": "17.3.2",
4
+ "version": "17.3.3",
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",
@@ -33,8 +33,8 @@
33
33
  "fmt": "biome format --write ."
34
34
  },
35
35
  "dependencies": {
36
- "@oh-my-pi/pi-natives": "17.3.2",
37
- "@oh-my-pi/pi-utils": "17.3.2"
36
+ "@oh-my-pi/pi-natives": "17.3.3",
37
+ "@oh-my-pi/pi-utils": "17.3.3"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/bun": "^1.3.14"
package/src/tokenizer.ts CHANGED
@@ -182,6 +182,37 @@ function scanRangeSeparator(line: string, index: number, end: number): number |
182
182
  return cursor;
183
183
  }
184
184
 
185
+ /**
186
+ * Recover a dangling range separator: the run after `N` contains at least one
187
+ * non-whitespace separator char but no end number (`244.=:`, `5-`, `12.. @reg`).
188
+ * Models write this intending an open range; it collapses to `N.=N`. Returns
189
+ * the index past the run only when what follows is `:`, `@`, or end-of-header —
190
+ * anything else keeps the header on the strict rejection path.
191
+ */
192
+ function scanDanglingSeparator(line: string, index: number, end: number): number | null {
193
+ let cursor = index;
194
+ let sawSeparatorChar = false;
195
+ while (cursor < end) {
196
+ const code = line.charCodeAt(cursor);
197
+ if (code === CHAR_HYPHEN || code === CHAR_DOT || code === CHAR_EQUALS || code === CHAR_ELLIPSIS) {
198
+ sawSeparatorChar = true;
199
+ cursor++;
200
+ continue;
201
+ }
202
+ if (isWhitespaceCode(code)) {
203
+ cursor++;
204
+ continue;
205
+ }
206
+ break;
207
+ }
208
+ if (!sawSeparatorChar) return null;
209
+ if (cursor < end) {
210
+ const code = line.charCodeAt(cursor);
211
+ if (code !== CHAR_COLON && code !== CHAR_AT) return null;
212
+ }
213
+ return cursor;
214
+ }
215
+
185
216
  function scanHeaderRange(line: string, index = 0, end = trimEndIndex(line), allowSingle = false): RangeScan | null {
186
217
  const numberStart = skipWhitespace(line, index, end);
187
218
  const start = scanLineNumber(line, numberStart, end);
@@ -189,6 +220,14 @@ function scanHeaderRange(line: string, index = 0, end = trimEndIndex(line), allo
189
220
  const afterFirst = scanRangeSeparator(line, start.nextIndex, end);
190
221
  if (afterFirst === null) {
191
222
  if (!allowSingle) return null;
223
+ const dangling = scanDanglingSeparator(line, start.nextIndex, end);
224
+ if (dangling !== null) {
225
+ return {
226
+ range: { start: { line: start.line }, end: { line: start.line } },
227
+ nextIndex: dangling,
228
+ hadSeparator: true,
229
+ };
230
+ }
192
231
  return {
193
232
  range: { start: { line: start.line }, end: { line: start.line } },
194
233
  nextIndex: skipWhitespace(line, start.nextIndex, end),