@oh-my-pi/hashline 15.11.8 → 15.12.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 CHANGED
@@ -2,6 +2,12 @@
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
+
5
11
  ## [15.11.4] - 2026-06-12
6
12
 
7
13
  ### Added
@@ -1,11 +1,13 @@
1
1
  import type { BlockResolution, BlockResolver, Edit } from "./types";
2
2
  export interface ResolveBlockEditsOptions {
3
3
  /**
4
- * How to handle a block edit that cannot be resolved (missing resolver or a
5
- * `null` span). `"throw"` (default) raises a `blockUnresolvedMessage` error —
6
- * used by the authoritative apply + final preview paths. `"drop"` silently
7
- * skips the edit — used by the streaming preview, where a half-written file
8
- * or transient parse error must not throw.
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.
9
11
  */
10
12
  onUnresolved?: "throw" | "drop";
11
13
  /**
@@ -16,9 +18,9 @@ export interface ResolveBlockEditsOptions {
16
18
  */
17
19
  onResolved?: (resolution: BlockResolution) => void;
18
20
  /**
19
- * Invoked once per diagnostic produced while resolving — currently only the
20
- * closer-anchor lowering of `insert after block N:`. Hosts should surface
21
- * these on the apply result's `warnings`.
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`.
22
24
  */
23
25
  onWarning?: (message: string) => void;
24
26
  }
@@ -1,117 +1,98 @@
1
- /**
2
- * Centralized error and warning text emitted by the hashline parser, applier,
3
- * and patcher. Consolidating these as named constants makes them easy to
4
- * audit and keeps wording stable across the rendering paths that surface
5
- * them.
6
- */
1
+ /** Centralized error/warning text for the hashline parser, applier, and patcher. */
7
2
  /** Lines of context shown either side of a hash mismatch. */
8
3
  export declare const MISMATCH_CONTEXT = 2;
9
4
  /**
10
- * Render numbered `LINE:TEXT` context rows around `anchorLines`
11
- * (±{@link MISMATCH_CONTEXT} lines each), `*`-marking the anchored lines and
12
- * separating non-adjacent runs with `...`. Out-of-range anchors contribute no
13
- * rows; returns an empty array when every anchor is out of range.
5
+ * Numbered `LINE:TEXT` rows around `anchorLines` (±{@link MISMATCH_CONTEXT}),
6
+ * `*`-marking anchors, `...` between non-adjacent runs. Out-of-range anchors
7
+ * contribute no rows.
14
8
  */
15
9
  export declare function formatAnchoredContext(anchorLines: readonly number[], fileLines: readonly string[]): string[];
16
- /** Optional patch envelope start marker; silently consumed when present. */
10
+ /** Optional patch envelope start marker; silently consumed. */
17
11
  export declare const BEGIN_PATCH_MARKER = "*** Begin Patch";
18
- /** Optional patch envelope end marker; terminates parsing when encountered. */
12
+ /** Optional patch envelope end marker; terminates parsing. */
19
13
  export declare const END_PATCH_MARKER = "*** End Patch";
20
14
  /**
21
- * Recovery sentinel emitted by an agent loop when a contaminated tool-call
22
- * stream is truncated mid-call. Behaves like {@link END_PATCH_MARKER} for
23
- * parsing — terminates the line loop — and does not surface a warning.
15
+ * Truncation sentinel emitted by an agent loop mid-call. Ends parsing like
16
+ * {@link END_PATCH_MARKER}, without a warning.
24
17
  */
25
18
  export declare const ABORT_MARKER = "*** Abort";
26
- /** Warning text appended when two consecutive hunks target the exact same concrete range. */
27
- export declare const REPLACE_PAIR_COALESCED_WARNING = "Detected two identical-range hashline hunks; kept only the second hunk. Issue ONE `replace N..M:` hunk per range \u2014 payload is the final desired content, never both old and new.";
28
- /** Warning text appended when an empty bodyless hunk is followed by an overlapping concrete hunk. */
29
- export declare const REPLACE_PAIR_COALESCED_OVERLAP_WARNING = "Detected an overlapping bare hashline hunk immediately followed by a concrete hunk; dropped the earlier bare hunk. Issue ONE `replace N..M:` hunk per range \u2014 payload is the final desired content, never both old and new.";
30
- /** Warning text appended when bare body rows are auto-converted to literal rows. */
31
- export declare const BARE_BODY_AUTO_PIPED_WARNING = "Auto-prefixed bare body row(s) with `+`. Body rows must be `+TEXT` literal lines; pasting raw code as payload is not a portable shape.";
32
- /** Error text emitted when a hunk body contains a unified-diff-style `-` row. */
33
- export declare const MINUS_ROW_REJECTED = "`-` rows are not valid; hashline ranges already name the lines being changed. To insert a literal line starting with `-`, write `+-\u2026`.";
34
- /** Error text emitted when a replace hunk has no body. */
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.";
23
+ /** Bare body rows auto-converted to literal `+` rows. */
24
+ export declare const BARE_BODY_AUTO_PIPED_WARNING = "Auto-prefixed bare body row(s) with `+`. Body rows must be `+TEXT` literal lines.";
25
+ /** Unified-diff-style `-` row in a hunk body. */
26
+ 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
+ /** Replace hunk with no body. */
35
28
  export declare const EMPTY_REPLACE = "`replace N..M:` needs at least one `+TEXT` body row. To delete lines, use `delete N..M`.";
36
- /** Error text emitted when a `replace block N:` hunk has no body. */
37
- export declare const EMPTY_BLOCK = "`replace block N:` needs at least one `+TEXT` body row. To delete a block, use `delete N..M` with the block's line range.";
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`.";
38
31
  /**
39
- * Error text emitted when a block-anchored op cannot be resolved to a
40
- * syntactic block (unrecognized language, blank/out-of-range line, no node
41
- * begins on line N such as a lone closing delimiter, or the resolved block has
42
- * a syntax error). Names the offending line, steers back to an explicit
43
- * concrete-line form, and — when `fileLines` is provided appends a
44
- * {@link formatAnchoredContext} preview of the file around the anchor line.
32
+ * Block-anchored replace/delete could not resolve to a syntactic block
33
+ * (unsupported language, blank/out-of-range line, no node beginning on N, or
34
+ * parse error). Appends a {@link formatAnchoredContext} preview when
35
+ * `fileLines` is given. `insert after block N:` never reaches this it is
36
+ * lowered to plain `insert after N:` instead (see
37
+ * {@link insertAfterBlockUnresolvedLoweredWarning}).
45
38
  */
46
- export declare function blockUnresolvedMessage(line: number, op?: "replace" | "delete" | "insert_after", fileLines?: readonly string[]): string;
39
+ export declare function blockUnresolvedMessage(line: number, op?: "replace" | "delete", fileLines?: readonly string[]): string;
40
+ /** 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.";
47
42
  /**
48
- * Error text emitted when a block-anchored edit reaches a code path that
49
- * has no {@link BlockResolver} wired in. Indicates a host-configuration bug
50
- * rather than authored-input error.
43
+ * `insert after block N:` anchored on a closing-delimiter line, lowered to
44
+ * plain `insert after N:` the closer ends a block, and inserting after it
45
+ * is exactly what the plain form does.
51
46
  */
52
- export declare const BLOCK_RESOLVER_UNAVAILABLE = "Block-anchored ops (`replace block N:`, `delete block N`, `insert after block N:`) are not available here (no tree-sitter block resolver is configured). Use a concrete line range instead.";
47
+ export declare function insertAfterBlockCloserLoweredWarning(line: number): string;
53
48
  /**
54
- * Warning emitted when an `insert after block N:` anchored on a pure
55
- * closing-delimiter line is lowered to plain `insert after N:`. No block
56
- * begins on a closer, but the closer IS the end of one — and inserting after
57
- * the end of that block is exactly what the plain form does.
49
+ * `insert after block N:` anchor unresolvable (unsupported language, blank
50
+ * line, parse error, or no resolver), lowered to plain `insert after N:`
51
+ * applying with a warning beats failing the patch.
58
52
  */
59
- export declare function insertAfterBlockCloserLoweredWarning(line: number): string;
53
+ export declare function insertAfterBlockUnresolvedLoweredWarning(line: number): string;
60
54
  /**
61
- * Internal invariant error: `applyEdits` received an unresolved `replace block
62
- * N:` edit. Block edits must be expanded by `resolveBlockEdits` before reaching
63
- * the applier; hitting this is a wiring bug, not authored-input error.
55
+ * Internal invariant: `applyEdits` received an unresolved `replace block N:`
56
+ * edit; `resolveBlockEdits` must run first. Wiring bug, not authored input.
64
57
  */
65
58
  export declare const UNRESOLVED_BLOCK_INTERNAL = "internal error: unresolved `replace block` edit reached the applier (resolveBlockEdits was not run).";
66
- /** Error text emitted when a delete hunk receives a body row. */
59
+ /** Delete hunk received a body row. */
67
60
  export declare const DELETE_TAKES_NO_BODY = "`delete N..M` does not take body rows. Remove the body, or use `replace N..M:`.";
68
- /** Error text emitted when a `delete block N` hunk receives a body row. */
69
- export declare const DELETE_BLOCK_TAKES_NO_BODY = "`delete block N` does not take body rows. Remove the body, or use `replace block N:` to replace the block.";
70
- /** Error text emitted when an insert hunk has no body. */
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:`.";
63
+ /** Insert hunk with no body. */
71
64
  export declare const EMPTY_INSERT = "`insert` needs at least one `+TEXT` body row.";
72
65
  /**
73
- * Warning emitted when an `insert after` edit's body rows are indented
74
- * shallower than the anchor line and the landing point was slid forward past
75
- * the structural closer lines that follow. The body's indentation names the
76
- * depth the author wants the new lines to sit at; anchoring inside a deeper
77
- * construct is the common "insert after the block, anchored on the last line
78
- * I read" mistake.
66
+ * `insert after` body indented shallower than the anchor: the landing slid
67
+ * forward past trailing closer lines the common "anchored on the last line
68
+ * I read instead of after the block" mistake.
79
69
  */
80
70
  export declare function afterInsertLandingShiftWarning(anchorLine: number, landingLine: number, crossed: number): string;
81
71
  /**
82
- * Warning emitted when an `insert after block N:` body is indented deeper
83
- * than the block's closing line and the landing was pulled back inside the
84
- * block. `insert after block` places content AFTER the block at sibling
85
- * depth; a deeper body almost always means "append inside the block's body"
86
- * — the misuse this corrects.
72
+ * `insert after block N:` body indented deeper than the block's closer: the
73
+ * landing was pulled inside the block a deeper body almost always means
74
+ * "append inside the block's body".
87
75
  */
88
76
  export declare function blockInsertLandingShiftWarning(blockStart: number, closerLine: number, landingLine: number): string;
89
- /** Warning text emitted by `Recovery` when an external write fits a cached snapshot. */
77
+ /** `Recovery`: an external write matched a cached snapshot. */
90
78
  export declare const RECOVERY_EXTERNAL_WARNING = "Recovered from a stale file hash using a previous read snapshot (file changed externally between read and edit).";
91
- /** Warning text emitted by `Recovery` when a prior in-session edit advanced the hash. */
92
- export declare const RECOVERY_SESSION_CHAIN_WARNING = "Recovered from a stale file hash using an earlier in-session snapshot (the file hash advanced after a prior edit in this session).";
79
+ /** `Recovery`: a prior in-session edit advanced the hash. */
80
+ export declare const RECOVERY_SESSION_CHAIN_WARNING = "Recovered from a stale file hash using an earlier in-session snapshot (a prior edit in this session advanced the hash).";
93
81
  /**
94
- * Warning text emitted by `Recovery` when the session-chain replay
95
- * fast-path was taken. Distinct from {@link RECOVERY_SESSION_CHAIN_WARNING}
96
- * because replay is the less-certain mode: the structured-patch 3-way
97
- * merge refused, the anchor-content gate passed, but a coincidental
98
- * insert+delete pair earlier in the chain could still leave an anchor's
99
- * line number pointing at a duplicated row. Surface the hedge so the
100
- * model verifies before continuing.
82
+ * `Recovery`: session-chain replay fast-path. Less certain than
83
+ * {@link RECOVERY_SESSION_CHAIN_WARNING} the 3-way merge refused, the
84
+ * anchor-content gate passed, but a coincidental insert+delete earlier in
85
+ * the chain could still misplace an anchor hence the verify hedge.
101
86
  */
102
- export declare const RECOVERY_SESSION_REPLAY_WARNING = "Recovered by replaying your edits onto the current file content \u2014 your previous edit in this session changed line(s) you re-targeted with a stale hash. Verify the diff matches your intent before continuing.";
87
+ export declare const RECOVERY_SESSION_REPLAY_WARNING = "Recovered by replaying your edits onto the current file content (a prior in-session edit changed the lines you re-targeted with a stale hash). Verify the diff matches your intent.";
103
88
  /**
104
- * Warning emitted when an `insert head:` / `insert tail:` edit is applied to an
105
- * existing file whose snapshot tag is stale (the file drifted since the read).
106
- * Head/tail insert position is content-independent "start"/"end" cannot move
107
- * with drift — so this is non-fatal: the edit applies onto the live content and
108
- * we surface the drift instead of hard-failing (unlike an anchored mismatch).
89
+ * `insert head:`/`insert tail:` applied despite a stale snapshot tag.
90
+ * Head/tail position is content-independent, so drift is non-fatal: apply
91
+ * onto live content and warn instead of hard-failing.
109
92
  */
110
- export declare const HEADTAIL_DRIFT_WARNING = "Applied an `insert head:`/`insert tail:` edit onto the current file content even though the snapshot tag was stale (the file changed since your read). Head/tail position is content-independent, so the insert was not rejected \u2014 but re-read if the drift was unexpected.";
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.";
111
94
  /**
112
- * Error text emitted when a hashline section omits the mandatory snapshot tag.
113
- * The tag is REQUIRED on every section, enforced identically by the apply path
114
- * ({@link Patcher.prepare}) and the preview/diff path, so both surfaces reuse
115
- * this single builder to stay in lockstep.
95
+ * Section omitted the mandatory snapshot tag. Shared by the apply
96
+ * ({@link Patcher.prepare}) and preview/diff paths so both stay in lockstep.
116
97
  */
117
98
  export declare function missingSnapshotTagMessage(sectionPath: string): string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/hashline",
4
- "version": "15.11.8",
4
+ "version": "15.12.1",
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/block.ts CHANGED
@@ -13,16 +13,23 @@
13
13
  * ever see resolved edits.
14
14
  */
15
15
  import { STRUCTURAL_CLOSER_RE } from "./apply";
16
- import { BLOCK_RESOLVER_UNAVAILABLE, blockUnresolvedMessage, insertAfterBlockCloserLoweredWarning } from "./messages";
16
+ import {
17
+ BLOCK_RESOLVER_UNAVAILABLE,
18
+ blockUnresolvedMessage,
19
+ insertAfterBlockCloserLoweredWarning,
20
+ insertAfterBlockUnresolvedLoweredWarning,
21
+ } from "./messages";
17
22
  import type { BlockResolution, BlockResolver, Cursor, Edit } from "./types";
18
23
 
19
24
  export interface ResolveBlockEditsOptions {
20
25
  /**
21
- * How to handle a block edit that cannot be resolved (missing resolver or a
22
- * `null` span). `"throw"` (default) raises a `blockUnresolvedMessage` error —
23
- * used by the authoritative apply + final preview paths. `"drop"` silently
24
- * skips the edit — used by the streaming preview, where a half-written file
25
- * or transient parse error must not throw.
26
+ * How to handle a replace/delete block edit that cannot be resolved
27
+ * (missing resolver or a `null` span). `"throw"` (default) raises a
28
+ * `blockUnresolvedMessage` error — used by the authoritative apply + final
29
+ * preview paths. `"drop"` silently skips the edit — used by the streaming
30
+ * preview, where a half-written file or transient parse error must not
31
+ * throw. Unresolvable `insert after block N:` edits never reach this: they
32
+ * are lowered to plain `insert after N:` with a warning.
26
33
  */
27
34
  onUnresolved?: "throw" | "drop";
28
35
  /**
@@ -33,9 +40,9 @@ export interface ResolveBlockEditsOptions {
33
40
  */
34
41
  onResolved?: (resolution: BlockResolution) => void;
35
42
  /**
36
- * Invoked once per diagnostic produced while resolving — currently only the
37
- * closer-anchor lowering of `insert after block N:`. Hosts should surface
38
- * these on the apply result's `warnings`.
43
+ * Invoked once per diagnostic produced while resolving — currently the
44
+ * `insert after block N:` lowerings (closer anchor or unresolvable block).
45
+ * Hosts should surface these on the apply result's `warnings`.
39
46
  */
40
47
  onWarning?: (message: string) => void;
41
48
  }
@@ -74,21 +81,27 @@ export function resolveBlockEdits(
74
81
  const op = edit.mode === "insert_after" ? "insert_after" : edit.payloads.length === 0 ? "delete" : "replace";
75
82
  const span = resolver ? resolver({ path, text, line: edit.anchor.line }) : null;
76
83
  if (span === null) {
77
- // `insert after block N` anchored on a pure closing-delimiter line:
78
- // no block begins there, but line N IS the end of one — and "after
79
- // the end of the block" is exactly plain `insert after N:`. Lower it
80
- // instead of failing the patch; warn so the author learns the
81
- // opener-only rule.
82
- if (op === "insert_after" && resolver) {
84
+ // `insert after block N:` never fails the patch lower it to plain
85
+ // `insert after N:` with a warning instead. Two flavors:
86
+ // - anchored on a pure closing-delimiter line: no block begins
87
+ // there, but line N IS the end of one, and "after the end of the
88
+ // block" is exactly the plain form — warn with the opener rule.
89
+ // - otherwise (unsupported language, blank line, unparsable block,
90
+ // or no resolver wired): "after the block at N" degrades to
91
+ // "after line N" — warn to verify the landing line.
92
+ if (op === "insert_after") {
83
93
  const anchorText = text.split("\n")[edit.anchor.line - 1];
84
- if (anchorText !== undefined && STRUCTURAL_CLOSER_RE.test(anchorText)) {
85
- options.onWarning?.(insertAfterBlockCloserLoweredWarning(edit.anchor.line));
86
- for (const payload of edit.payloads) {
87
- const cursor: Cursor = { kind: "after_anchor", anchor: { line: edit.anchor.line } };
88
- resolved.push({ kind: "insert", cursor, text: payload, lineNum: edit.lineNum, index: synthIndex++ });
89
- }
90
- continue;
94
+ const isCloser = anchorText !== undefined && STRUCTURAL_CLOSER_RE.test(anchorText);
95
+ options.onWarning?.(
96
+ isCloser
97
+ ? insertAfterBlockCloserLoweredWarning(edit.anchor.line)
98
+ : insertAfterBlockUnresolvedLoweredWarning(edit.anchor.line),
99
+ );
100
+ for (const payload of edit.payloads) {
101
+ const cursor: Cursor = { kind: "after_anchor", anchor: { line: edit.anchor.line } };
102
+ resolved.push({ kind: "insert", cursor, text: payload, lineNum: edit.lineNum, index: synthIndex++ });
91
103
  }
104
+ continue;
92
105
  }
93
106
  if (onUnresolved === "drop") continue;
94
107
  throw new Error(
package/src/messages.ts CHANGED
@@ -1,9 +1,4 @@
1
- /**
2
- * Centralized error and warning text emitted by the hashline parser, applier,
3
- * and patcher. Consolidating these as named constants makes them easy to
4
- * audit and keeps wording stable across the rendering paths that surface
5
- * them.
6
- */
1
+ /** Centralized error/warning text for the hashline parser, applier, and patcher. */
7
2
 
8
3
  import { formatNumberedLine, HL_FILE_HASH_SEP, HL_FILE_PREFIX, HL_FILE_SUFFIX } from "./format";
9
4
 
@@ -11,10 +6,9 @@ import { formatNumberedLine, HL_FILE_HASH_SEP, HL_FILE_PREFIX, HL_FILE_SUFFIX }
11
6
  export const MISMATCH_CONTEXT = 2;
12
7
 
13
8
  /**
14
- * Render numbered `LINE:TEXT` context rows around `anchorLines`
15
- * (±{@link MISMATCH_CONTEXT} lines each), `*`-marking the anchored lines and
16
- * separating non-adjacent runs with `...`. Out-of-range anchors contribute no
17
- * rows; returns an empty array when every anchor is out of range.
9
+ * Numbered `LINE:TEXT` rows around `anchorLines` (±{@link MISMATCH_CONTEXT}),
10
+ * `*`-marking anchors, `...` between non-adjacent runs. Out-of-range anchors
11
+ * contribute no rows.
18
12
  */
19
13
  export function formatAnchoredContext(anchorLines: readonly number[], fileLines: readonly string[]): string[] {
20
14
  const displayLines = new Set<number>();
@@ -36,71 +30,59 @@ export function formatAnchoredContext(anchorLines: readonly number[], fileLines:
36
30
  return rows;
37
31
  }
38
32
 
39
- /** Optional patch envelope start marker; silently consumed when present. */
33
+ /** Optional patch envelope start marker; silently consumed. */
40
34
  export const BEGIN_PATCH_MARKER = "*** Begin Patch";
41
35
 
42
- /** Optional patch envelope end marker; terminates parsing when encountered. */
36
+ /** Optional patch envelope end marker; terminates parsing. */
43
37
  export const END_PATCH_MARKER = "*** End Patch";
44
38
 
45
39
  /**
46
- * Recovery sentinel emitted by an agent loop when a contaminated tool-call
47
- * stream is truncated mid-call. Behaves like {@link END_PATCH_MARKER} for
48
- * parsing — terminates the line loop — and does not surface a warning.
40
+ * Truncation sentinel emitted by an agent loop mid-call. Ends parsing like
41
+ * {@link END_PATCH_MARKER}, without a warning.
49
42
  */
50
43
  export const ABORT_MARKER = "*** Abort";
51
44
 
52
- /** Warning text appended when two consecutive hunks target the exact same concrete range. */
45
+ /** Two consecutive hunks targeted the exact same concrete range. */
53
46
  export const REPLACE_PAIR_COALESCED_WARNING =
54
- "Detected two identical-range hashline hunks; kept only the second hunk. Issue ONE `replace N..M:` hunk per range — payload is the final desired content, never both old and new.";
47
+ "Two hunks targeted the same range; kept only the second. One `replace N..M:` hunk per range — the body is the final content, never old+new.";
55
48
 
56
- /** Warning text appended when an empty bodyless hunk is followed by an overlapping concrete hunk. */
49
+ /** Bare bodyless hunk followed by an overlapping concrete hunk. */
57
50
  export const REPLACE_PAIR_COALESCED_OVERLAP_WARNING =
58
- "Detected an overlapping bare hashline hunk immediately followed by a concrete hunk; dropped the earlier bare hunk. Issue ONE `replace N..M:` hunk per range — payload is the final desired content, never both old and new.";
51
+ "Dropped a bare hunk overlapped by the concrete hunk after it. One `replace N..M:` hunk per range — the body is the final content, never old+new.";
59
52
 
60
- /** Warning text appended when bare body rows are auto-converted to literal rows. */
53
+ /** Bare body rows auto-converted to literal `+` rows. */
61
54
  export const BARE_BODY_AUTO_PIPED_WARNING =
62
- "Auto-prefixed bare body row(s) with `+`. Body rows must be `+TEXT` literal lines; pasting raw code as payload is not a portable shape.";
55
+ "Auto-prefixed bare body row(s) with `+`. Body rows must be `+TEXT` literal lines.";
63
56
 
64
- /** Error text emitted when a hunk body contains a unified-diff-style `-` row. */
57
+ /** Unified-diff-style `-` row in a hunk body. */
65
58
  export const MINUS_ROW_REJECTED =
66
- "`-` rows are not valid; hashline ranges already name the lines being changed. To insert a literal line starting with `-`, write `+-…`.";
59
+ "`-` rows are not valid; the range already names the lines being changed. For a literal `-` line, write `+-…`.";
67
60
 
68
- /** Error text emitted when a replace hunk has no body. */
61
+ /** Replace hunk with no body. */
69
62
  export const EMPTY_REPLACE = "`replace N..M:` needs at least one `+TEXT` body row. To delete lines, use `delete N..M`.";
70
63
 
71
- /** Error text emitted when a `replace block N:` hunk has no body. */
64
+ /** `replace block N:` hunk with no body. */
72
65
  export const EMPTY_BLOCK =
73
- "`replace block N:` needs at least one `+TEXT` body row. To delete a block, use `delete N..M` with the block's line range.";
66
+ "`replace block N:` needs at least one `+TEXT` body row. To delete a block, use `delete block N`.";
74
67
 
75
68
  /**
76
- * Error text emitted when a block-anchored op cannot be resolved to a
77
- * syntactic block (unrecognized language, blank/out-of-range line, no node
78
- * begins on line N such as a lone closing delimiter, or the resolved block has
79
- * a syntax error). Names the offending line, steers back to an explicit
80
- * concrete-line form, and — when `fileLines` is provided appends a
81
- * {@link formatAnchoredContext} preview of the file around the anchor line.
69
+ * Block-anchored replace/delete could not resolve to a syntactic block
70
+ * (unsupported language, blank/out-of-range line, no node beginning on N, or
71
+ * parse error). Appends a {@link formatAnchoredContext} preview when
72
+ * `fileLines` is given. `insert after block N:` never reaches this it is
73
+ * lowered to plain `insert after N:` instead (see
74
+ * {@link insertAfterBlockUnresolvedLoweredWarning}).
82
75
  */
83
76
  export function blockUnresolvedMessage(
84
77
  line: number,
85
- op: "replace" | "delete" | "insert_after" = "replace",
78
+ op: "replace" | "delete" = "replace",
86
79
  fileLines?: readonly string[],
87
80
  ): string {
88
- const phrase =
89
- op === "delete"
90
- ? `delete block ${line}`
91
- : op === "insert_after"
92
- ? `insert after block ${line}:`
93
- : `replace block ${line}:`;
94
- const fallback =
95
- op === "delete"
96
- ? `\`delete ${line}..M\``
97
- : op === "insert_after"
98
- ? `\`insert after M:\` with the block's explicit last line`
99
- : `\`replace ${line}..M:\` with the block's explicit end line`;
81
+ const phrase = op === "delete" ? `delete block ${line}` : `replace block ${line}:`;
82
+ const fallback = op === "delete" ? `delete ${line}..M` : `replace ${line}..M:`;
100
83
  let message =
101
- `\`${phrase}\` could not resolve a syntactic block beginning on line ${line}. ` +
102
- `The language may be unsupported, the line may be blank or a closing delimiter, or the block may not parse. ` +
103
- `Use ${fallback} instead.`;
84
+ `\`${phrase}\` could not resolve a syntactic block beginning on line ${line} ` +
85
+ `(unsupported language, blank/closer line, or parse error). Use \`${fallback}\` with explicit lines.`;
104
86
  if (fileLines) {
105
87
  const context = formatAnchoredContext([line], fileLines);
106
88
  if (context.length > 0) message += `\n\n${context.join("\n")}`;
@@ -108,113 +90,92 @@ export function blockUnresolvedMessage(
108
90
  return message;
109
91
  }
110
92
 
111
- /**
112
- * Error text emitted when a block-anchored edit reaches a code path that
113
- * has no {@link BlockResolver} wired in. Indicates a host-configuration bug
114
- * rather than authored-input error.
115
- */
93
+ /** Block-anchored edit reached a path with no {@link BlockResolver} wired in — a host-configuration bug. */
116
94
  export const BLOCK_RESOLVER_UNAVAILABLE =
117
- "Block-anchored ops (`replace block N:`, `delete block N`, `insert after block N:`) are not available here (no tree-sitter block resolver is configured). Use a concrete line range instead.";
95
+ "`replace block`/`delete block`/`insert after block` are not available here (no block resolver configured). Use a concrete line range.";
118
96
 
119
97
  /**
120
- * Warning emitted when an `insert after block N:` anchored on a pure
121
- * closing-delimiter line is lowered to plain `insert after N:`. No block
122
- * begins on a closer, but the closer IS the end of one — and inserting after
123
- * the end of that block is exactly what the plain form does.
98
+ * `insert after block N:` anchored on a closing-delimiter line, lowered to
99
+ * plain `insert after N:` the closer ends a block, and inserting after it
100
+ * is exactly what the plain form does.
124
101
  */
125
102
  export function insertAfterBlockCloserLoweredWarning(line: number): string {
126
- return (
127
- `\`insert after block ${line}:\` anchors on a closing-delimiter line; no block begins there, so it was applied as plain \`insert after ${line}:\`. ` +
128
- "Anchor `insert after block` on the line that OPENS the construct."
129
- );
103
+ return `\`insert after block ${line}:\` anchors on a closing delimiter, so it was applied as plain \`insert after ${line}:\`. Anchor on the line that OPENS the construct.`;
104
+ }
105
+
106
+ /**
107
+ * `insert after block N:` anchor unresolvable (unsupported language, blank
108
+ * line, parse error, or no resolver), lowered to plain `insert after N:` —
109
+ * applying with a warning beats failing the patch.
110
+ */
111
+ export function insertAfterBlockUnresolvedLoweredWarning(line: number): string {
112
+ return `\`insert after block ${line}:\` could not resolve a syntactic block on line ${line}, so it was applied as plain \`insert after ${line}:\`. Verify the landing line; anchor on a line that OPENS a construct.`;
130
113
  }
131
114
 
132
115
  /**
133
- * Internal invariant error: `applyEdits` received an unresolved `replace block
134
- * N:` edit. Block edits must be expanded by `resolveBlockEdits` before reaching
135
- * the applier; hitting this is a wiring bug, not authored-input error.
116
+ * Internal invariant: `applyEdits` received an unresolved `replace block N:`
117
+ * edit; `resolveBlockEdits` must run first. Wiring bug, not authored input.
136
118
  */
137
119
  export const UNRESOLVED_BLOCK_INTERNAL =
138
120
  "internal error: unresolved `replace block` edit reached the applier (resolveBlockEdits was not run).";
139
121
 
140
- /** Error text emitted when a delete hunk receives a body row. */
122
+ /** Delete hunk received a body row. */
141
123
  export const DELETE_TAKES_NO_BODY = "`delete N..M` does not take body rows. Remove the body, or use `replace N..M:`.";
142
124
 
143
- /** Error text emitted when a `delete block N` hunk receives a body row. */
125
+ /** `delete block N` hunk received a body row. */
144
126
  export const DELETE_BLOCK_TAKES_NO_BODY =
145
- "`delete block N` does not take body rows. Remove the body, or use `replace block N:` to replace the block.";
127
+ "`delete block N` does not take body rows. Remove the body, or use `replace block N:`.";
146
128
 
147
- /** Error text emitted when an insert hunk has no body. */
129
+ /** Insert hunk with no body. */
148
130
  export const EMPTY_INSERT = "`insert` needs at least one `+TEXT` body row.";
149
131
 
150
132
  /**
151
- * Warning emitted when an `insert after` edit's body rows are indented
152
- * shallower than the anchor line and the landing point was slid forward past
153
- * the structural closer lines that follow. The body's indentation names the
154
- * depth the author wants the new lines to sit at; anchoring inside a deeper
155
- * construct is the common "insert after the block, anchored on the last line
156
- * I read" mistake.
133
+ * `insert after` body indented shallower than the anchor: the landing slid
134
+ * forward past trailing closer lines the common "anchored on the last line
135
+ * I read instead of after the block" mistake.
157
136
  */
158
137
  export function afterInsertLandingShiftWarning(anchorLine: number, landingLine: number, crossed: number): string {
159
- return (
160
- `insert after ${anchorLine}: the body is indented shallower than line ${anchorLine}, so the landing was moved past ` +
161
- `${crossed} closing line${crossed === 1 ? "" : "s"} to after line ${landingLine}. ` +
162
- `If you meant the deeper position inside the block, re-issue with the body indented to match.`
163
- );
138
+ return `insert after ${anchorLine}: body indented shallower than the anchor, so the landing moved past ${crossed} closing line${crossed === 1 ? "" : "s"} to after line ${landingLine}. For the deeper position inside the block, re-issue with the body indented to match.`;
164
139
  }
165
140
 
166
141
  /**
167
- * Warning emitted when an `insert after block N:` body is indented deeper
168
- * than the block's closing line and the landing was pulled back inside the
169
- * block. `insert after block` places content AFTER the block at sibling
170
- * depth; a deeper body almost always means "append inside the block's body"
171
- * — the misuse this corrects.
142
+ * `insert after block N:` body indented deeper than the block's closer: the
143
+ * landing was pulled inside the block a deeper body almost always means
144
+ * "append inside the block's body".
172
145
  */
173
146
  export function blockInsertLandingShiftWarning(blockStart: number, closerLine: number, landingLine: number): string {
174
- return (
175
- `insert after block ${blockStart}: the body is indented deeper than the block's closing line ${closerLine}, ` +
176
- `so it was placed inside the block, after line ${landingLine}. ` +
177
- "`insert after block` always lands AFTER the block at sibling depth — if you meant that, " +
178
- `re-issue as plain \`insert after ${closerLine}:\` with the body indented to match line ${closerLine}.`
179
- );
147
+ return `insert after block ${blockStart}: body indented deeper than closing line ${closerLine}, so it was placed inside the block, after line ${landingLine}. \`insert after block\` lands AFTER the block at sibling depth — if inside was intended, use plain \`insert after ${closerLine}:\`.`;
180
148
  }
181
149
 
182
- /** Warning text emitted by `Recovery` when an external write fits a cached snapshot. */
150
+ /** `Recovery`: an external write matched a cached snapshot. */
183
151
  export const RECOVERY_EXTERNAL_WARNING =
184
152
  "Recovered from a stale file hash using a previous read snapshot (file changed externally between read and edit).";
185
153
 
186
- /** Warning text emitted by `Recovery` when a prior in-session edit advanced the hash. */
154
+ /** `Recovery`: a prior in-session edit advanced the hash. */
187
155
  export const RECOVERY_SESSION_CHAIN_WARNING =
188
- "Recovered from a stale file hash using an earlier in-session snapshot (the file hash advanced after a prior edit in this session).";
156
+ "Recovered from a stale file hash using an earlier in-session snapshot (a prior edit in this session advanced the hash).";
189
157
 
190
158
  /**
191
- * Warning text emitted by `Recovery` when the session-chain replay
192
- * fast-path was taken. Distinct from {@link RECOVERY_SESSION_CHAIN_WARNING}
193
- * because replay is the less-certain mode: the structured-patch 3-way
194
- * merge refused, the anchor-content gate passed, but a coincidental
195
- * insert+delete pair earlier in the chain could still leave an anchor's
196
- * line number pointing at a duplicated row. Surface the hedge so the
197
- * model verifies before continuing.
159
+ * `Recovery`: session-chain replay fast-path. Less certain than
160
+ * {@link RECOVERY_SESSION_CHAIN_WARNING} the 3-way merge refused, the
161
+ * anchor-content gate passed, but a coincidental insert+delete earlier in
162
+ * the chain could still misplace an anchor hence the verify hedge.
198
163
  */
199
164
  export const RECOVERY_SESSION_REPLAY_WARNING =
200
- "Recovered by replaying your edits onto the current file content your previous edit in this session changed line(s) you re-targeted with a stale hash. Verify the diff matches your intent before continuing.";
165
+ "Recovered by replaying your edits onto the current file content (a prior in-session edit changed the lines you re-targeted with a stale hash). Verify the diff matches your intent.";
201
166
 
202
167
  /**
203
- * Warning emitted when an `insert head:` / `insert tail:` edit is applied to an
204
- * existing file whose snapshot tag is stale (the file drifted since the read).
205
- * Head/tail insert position is content-independent "start"/"end" cannot move
206
- * with drift — so this is non-fatal: the edit applies onto the live content and
207
- * we surface the drift instead of hard-failing (unlike an anchored mismatch).
168
+ * `insert head:`/`insert tail:` applied despite a stale snapshot tag.
169
+ * Head/tail position is content-independent, so drift is non-fatal: apply
170
+ * onto live content and warn instead of hard-failing.
208
171
  */
209
172
  export const HEADTAIL_DRIFT_WARNING =
210
- "Applied an `insert head:`/`insert tail:` edit onto the current file content even though the snapshot tag was stale (the file changed since your read). Head/tail position is content-independent, so the insert was not rejected — but re-read if the drift was unexpected.";
173
+ "Applied the `insert head:`/`insert tail:` edit despite a stale snapshot tag (file changed since your read) — head/tail position is content-independent. Re-read if the drift was unexpected.";
211
174
 
212
175
  /**
213
- * Error text emitted when a hashline section omits the mandatory snapshot tag.
214
- * The tag is REQUIRED on every section, enforced identically by the apply path
215
- * ({@link Patcher.prepare}) and the preview/diff path, so both surfaces reuse
216
- * this single builder to stay in lockstep.
176
+ * Section omitted the mandatory snapshot tag. Shared by the apply
177
+ * ({@link Patcher.prepare}) and preview/diff paths so both stay in lockstep.
217
178
  */
218
179
  export function missingSnapshotTagMessage(sectionPath: string): string {
219
- return `Missing hashline snapshot tag for edit to ${sectionPath}; use \`${HL_FILE_PREFIX}${sectionPath}${HL_FILE_HASH_SEP}tag${HL_FILE_SUFFIX}\` from your latest read/search output. To create a new file, use the write tool.`;
180
+ return `Missing hashline snapshot tag for ${sectionPath}; use \`${HL_FILE_PREFIX}${sectionPath}${HL_FILE_HASH_SEP}tag${HL_FILE_SUFFIX}\` from your latest read/search output. To create a new file, use the write tool.`;
220
181
  }