@oh-my-pi/hashline 17.1.1 → 17.1.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 +6 -0
- package/dist/types/messages.d.ts +6 -0
- package/package.json +2 -2
- package/src/messages.ts +8 -0
- package/src/parser.ts +49 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [17.1.2] - 2026-07-24
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Bare `- ` bullet body rows are now auto-accepted as literal content with a warning when the hunk is unambiguously a Markdown bullet list (every `-` row bullet-shaped and no plain `+new` diff counterpart); ambiguous `-` rows still fail with the teaching error.
|
|
10
|
+
|
|
5
11
|
## [17.0.8] - 2026-07-22
|
|
6
12
|
|
|
7
13
|
### Changed
|
package/dist/types/messages.d.ts
CHANGED
|
@@ -20,6 +20,12 @@ export declare const ABORT_MARKER = "*** Abort";
|
|
|
20
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.";
|
|
21
21
|
/** Bare body rows auto-converted to literal `+` rows. */
|
|
22
22
|
export declare const BARE_BODY_AUTO_PIPED_WARNING = "Auto-prefixed bare body row(s) with `+`. Body rows must be `+TEXT` literal lines.";
|
|
23
|
+
/**
|
|
24
|
+
* Bare `-` body rows accepted as literal Markdown bullets. Only emitted when
|
|
25
|
+
* the hunk is unambiguously a bullet list: every `-` row is bullet-shaped
|
|
26
|
+
* (`- item`) and the body has no unified-diff `+new` counterpart rows.
|
|
27
|
+
*/
|
|
28
|
+
export declare const MINUS_BULLET_AUTO_PIPED_WARNING = "Auto-prefixed bare `- ` bullet row(s) as literal content. `-` rows never remove lines \u2014 the range does that; always prefix literal body rows with `+`: `+- item`.";
|
|
23
29
|
/** Unified-diff-style `-` row in a hunk body. */
|
|
24
30
|
export declare const MINUS_ROW_REJECTED = "`-` rows are not valid; the range already names the lines being changed. For Markdown bullets or other literal `-` lines, prefix the literal row with `+`: `+- item`.";
|
|
25
31
|
/** Replace hunk with no body. */
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/hashline",
|
|
4
|
-
"version": "17.1.
|
|
4
|
+
"version": "17.1.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",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"fmt": "biome format --write ."
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@oh-my-pi/pi-natives": "17.1.
|
|
36
|
+
"@oh-my-pi/pi-natives": "17.1.2",
|
|
37
37
|
"lru-cache": "11.5.2"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
package/src/messages.ts
CHANGED
|
@@ -52,6 +52,14 @@ export const REPLACE_PAIR_COALESCED_WARNING = `Two hunks targeted the same range
|
|
|
52
52
|
export const BARE_BODY_AUTO_PIPED_WARNING =
|
|
53
53
|
"Auto-prefixed bare body row(s) with `+`. Body rows must be `+TEXT` literal lines.";
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Bare `-` body rows accepted as literal Markdown bullets. Only emitted when
|
|
57
|
+
* the hunk is unambiguously a bullet list: every `-` row is bullet-shaped
|
|
58
|
+
* (`- item`) and the body has no unified-diff `+new` counterpart rows.
|
|
59
|
+
*/
|
|
60
|
+
export const MINUS_BULLET_AUTO_PIPED_WARNING =
|
|
61
|
+
"Auto-prefixed bare `- ` bullet row(s) as literal content. `-` rows never remove lines — the range does that; always prefix literal body rows with `+`: `+- item`.";
|
|
62
|
+
|
|
55
63
|
/** Unified-diff-style `-` row in a hunk body. */
|
|
56
64
|
export const MINUS_ROW_REJECTED =
|
|
57
65
|
"`-` rows are not valid; the range already names the lines being changed. For Markdown bullets or other literal `-` lines, prefix the literal row with `+`: `+- item`.";
|
package/src/parser.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
DELETE_TAKES_NO_BODY,
|
|
11
11
|
EMPTY_BLOCK,
|
|
12
12
|
EMPTY_INSERT,
|
|
13
|
+
MINUS_BULLET_AUTO_PIPED_WARNING,
|
|
13
14
|
MINUS_ROW_REJECTED,
|
|
14
15
|
MOVE_TAKES_NO_BODY,
|
|
15
16
|
REM_TAKES_NO_BODY,
|
|
@@ -43,6 +44,13 @@ function isSkippableCommentLine(line: string): boolean {
|
|
|
43
44
|
*/
|
|
44
45
|
const BARE_LITERAL_VALUE_RE = /^\s*(?:"[^"]*"|'[^']*'|[-+]?\d+(?:\.\d+)?)\s*,?\s*$/;
|
|
45
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Markdown-bullet shape: optional indent, `-`, exactly one space, then
|
|
49
|
+
* content. Unified-diff `-` rows almost never match — code lines get the `-`
|
|
50
|
+
* glued on (`-old()`) and indented deletions carry multiple spaces (`- x`).
|
|
51
|
+
*/
|
|
52
|
+
const MD_BULLET_ROW_RE = /^\s*- \S/;
|
|
53
|
+
|
|
46
54
|
function detectApplyPatchContamination(text: string, _hasPending: boolean): string | null {
|
|
47
55
|
const trimmed = text.trimStart();
|
|
48
56
|
if (trimmed.length === 0) return null;
|
|
@@ -93,7 +101,7 @@ interface PendingComment {
|
|
|
93
101
|
text: string;
|
|
94
102
|
}
|
|
95
103
|
|
|
96
|
-
type PayloadRow = { kind: "literal"; text: string; lineNum: number; bare?: boolean };
|
|
104
|
+
type PayloadRow = { kind: "literal"; text: string; lineNum: number; bare?: boolean; minus?: boolean };
|
|
97
105
|
|
|
98
106
|
interface Pending {
|
|
99
107
|
target: BlockTarget;
|
|
@@ -284,8 +292,12 @@ export class Executor {
|
|
|
284
292
|
if (this.#pending.target.kind === "delete") throw new Error(`line ${lineNum}: ${DELETE_TAKES_NO_BODY}`);
|
|
285
293
|
if (this.#pending.target.kind === "delete_block")
|
|
286
294
|
throw new Error(`line ${lineNum}: ${DELETE_BLOCK_TAKES_NO_BODY}`);
|
|
287
|
-
|
|
288
|
-
|
|
295
|
+
const row: PayloadRow = { kind: "literal", text, lineNum, bare: true };
|
|
296
|
+
// `-` rows are held and judged at flush time by #resolveMinusRows,
|
|
297
|
+
// once the whole body is visible.
|
|
298
|
+
if (text.trimStart().charCodeAt(0) === 45 /* - */) row.minus = true;
|
|
299
|
+
else if (!this.#warnings.includes(BARE_BODY_AUTO_PIPED_WARNING))
|
|
300
|
+
this.#warnings.push(BARE_BODY_AUTO_PIPED_WARNING);
|
|
289
301
|
this.#commitDeferredBlanks(this.#pending);
|
|
290
302
|
// Defer read-output line-number stripping to #flushPending: a bare
|
|
291
303
|
// "N:text" row is only a copy-paste artifact from snapshot output
|
|
@@ -294,7 +306,7 @@ export class Executor {
|
|
|
294
306
|
// with "digits:" (YAML ports "42:hello", timestamps "12:30") when it
|
|
295
307
|
// sits next to an unprefixed sibling. Rows with an explicit "+" go
|
|
296
308
|
// through #handleLiteralPayload and are never bare, never stripped.
|
|
297
|
-
this.#pending.payloads.push(
|
|
309
|
+
this.#pending.payloads.push(row);
|
|
298
310
|
return;
|
|
299
311
|
}
|
|
300
312
|
if (text.trim().length === 0) return;
|
|
@@ -326,6 +338,38 @@ export class Executor {
|
|
|
326
338
|
pending.deferredBlanks = [];
|
|
327
339
|
}
|
|
328
340
|
|
|
341
|
+
/**
|
|
342
|
+
* Judge bare `-` body rows once the whole hunk body is known. They are
|
|
343
|
+
* usually unified-diff contamination (`-old` next to `+new`) and inserting
|
|
344
|
+
* them would corrupt the file, so they are rejected — EXCEPT when the body
|
|
345
|
+
* is unambiguously a Markdown bullet list: every `-` row is bullet-shaped
|
|
346
|
+
* (`- item`) and the body is either fully bare or already contains an
|
|
347
|
+
* explicit `+- item` sibling. Those rows are kept as literal content with a
|
|
348
|
+
* warning instead of failing the patch.
|
|
349
|
+
*/
|
|
350
|
+
#resolveMinusRows(payloads: readonly PayloadRow[]): void {
|
|
351
|
+
let firstMinus: PayloadRow | undefined;
|
|
352
|
+
let allBulletShaped = true;
|
|
353
|
+
let hasExplicit = false;
|
|
354
|
+
let hasExplicitBullet = false;
|
|
355
|
+
for (const row of payloads) {
|
|
356
|
+
if (row.minus) {
|
|
357
|
+
firstMinus ??= row;
|
|
358
|
+
allBulletShaped &&= MD_BULLET_ROW_RE.test(row.text);
|
|
359
|
+
} else if (!row.bare) {
|
|
360
|
+
hasExplicit = true;
|
|
361
|
+
hasExplicitBullet ||= MD_BULLET_ROW_RE.test(row.text);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
if (firstMinus === undefined) return;
|
|
365
|
+
if (allBulletShaped && (!hasExplicit || hasExplicitBullet)) {
|
|
366
|
+
if (!this.#warnings.includes(MINUS_BULLET_AUTO_PIPED_WARNING))
|
|
367
|
+
this.#warnings.push(MINUS_BULLET_AUTO_PIPED_WARNING);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
throw new Error(`line ${firstMinus.lineNum}: ${MINUS_ROW_REJECTED}`);
|
|
371
|
+
}
|
|
372
|
+
|
|
329
373
|
/**
|
|
330
374
|
* Strip a single read-output line-number prefix (`N:`) from every bare body
|
|
331
375
|
* row, but only when *all* bare rows carry one. A uniform set of prefixes is
|
|
@@ -388,6 +432,7 @@ export class Executor {
|
|
|
388
432
|
const pending = this.#pending;
|
|
389
433
|
if (!pending) return;
|
|
390
434
|
const { target, lineNum, payloads } = pending;
|
|
435
|
+
this.#resolveMinusRows(payloads);
|
|
391
436
|
this.#stripBarePrefixesIfUniform(payloads);
|
|
392
437
|
this.#pending = undefined;
|
|
393
438
|
if (target.kind === "delete") {
|