@oh-my-pi/hashline 15.11.8 → 15.12.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 +6 -0
- package/dist/types/block.d.ts +10 -8
- package/dist/types/messages.d.ts +62 -81
- package/package.json +1 -1
- package/src/block.ts +35 -22
- package/src/messages.ts +74 -113
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
|
package/dist/types/block.d.ts
CHANGED
|
@@ -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
|
|
5
|
-
* `null` span). `"throw"` (default) raises a
|
|
6
|
-
* used by the authoritative apply + final
|
|
7
|
-
* skips the edit — used by the streaming
|
|
8
|
-
* or transient parse error must not
|
|
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
|
|
20
|
-
*
|
|
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
|
}
|
package/dist/types/messages.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
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
|
|
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
|
|
12
|
+
/** Optional patch envelope end marker; terminates parsing. */
|
|
19
13
|
export declare const END_PATCH_MARKER = "*** End Patch";
|
|
20
14
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
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
|
-
/**
|
|
27
|
-
export declare const REPLACE_PAIR_COALESCED_WARNING = "
|
|
28
|
-
/**
|
|
29
|
-
export declare const REPLACE_PAIR_COALESCED_OVERLAP_WARNING = "
|
|
30
|
-
/**
|
|
31
|
-
export declare const BARE_BODY_AUTO_PIPED_WARNING = "Auto-prefixed bare body row(s) with `+`. Body rows must be `+TEXT` literal lines
|
|
32
|
-
/**
|
|
33
|
-
export declare const MINUS_ROW_REJECTED = "`-` rows are not valid;
|
|
34
|
-
/**
|
|
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
|
-
/**
|
|
37
|
-
export declare const EMPTY_BLOCK = "`replace block N:` needs at least one `+TEXT` body row. To delete a block, use `delete
|
|
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
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* {@link
|
|
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"
|
|
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
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
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
|
|
47
|
+
export declare function insertAfterBlockCloserLoweredWarning(line: number): string;
|
|
53
48
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
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
|
|
53
|
+
export declare function insertAfterBlockUnresolvedLoweredWarning(line: number): string;
|
|
60
54
|
/**
|
|
61
|
-
* Internal invariant
|
|
62
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
|
70
|
-
/**
|
|
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
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
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
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
92
|
-
export declare const RECOVERY_SESSION_CHAIN_WARNING = "Recovered from a stale file hash using an earlier in-session snapshot (
|
|
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
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
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
|
|
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
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
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
|
|
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
|
-
*
|
|
113
|
-
*
|
|
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.
|
|
4
|
+
"version": "15.12.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/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 {
|
|
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
|
|
22
|
-
* `null` span). `"throw"` (default) raises a
|
|
23
|
-
* used by the authoritative apply + final
|
|
24
|
-
* skips the edit — used by the streaming
|
|
25
|
-
* or transient parse error must not
|
|
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
|
|
37
|
-
*
|
|
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
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
// opener
|
|
82
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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
|
|
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
|
|
36
|
+
/** Optional patch envelope end marker; terminates parsing. */
|
|
43
37
|
export const END_PATCH_MARKER = "*** End Patch";
|
|
44
38
|
|
|
45
39
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
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
|
-
/**
|
|
45
|
+
/** Two consecutive hunks targeted the exact same concrete range. */
|
|
53
46
|
export const REPLACE_PAIR_COALESCED_WARNING =
|
|
54
|
-
"
|
|
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
|
-
/**
|
|
49
|
+
/** Bare bodyless hunk followed by an overlapping concrete hunk. */
|
|
57
50
|
export const REPLACE_PAIR_COALESCED_OVERLAP_WARNING =
|
|
58
|
-
"
|
|
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
|
-
/**
|
|
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
|
|
55
|
+
"Auto-prefixed bare body row(s) with `+`. Body rows must be `+TEXT` literal lines.";
|
|
63
56
|
|
|
64
|
-
/**
|
|
57
|
+
/** Unified-diff-style `-` row in a hunk body. */
|
|
65
58
|
export const MINUS_ROW_REJECTED =
|
|
66
|
-
"`-` rows are not valid;
|
|
59
|
+
"`-` rows are not valid; the range already names the lines being changed. For a literal `-` line, write `+-…`.";
|
|
67
60
|
|
|
68
|
-
/**
|
|
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
|
-
/**
|
|
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
|
|
66
|
+
"`replace block N:` needs at least one `+TEXT` body row. To delete a block, use `delete block N`.";
|
|
74
67
|
|
|
75
68
|
/**
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* {@link
|
|
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"
|
|
78
|
+
op: "replace" | "delete" = "replace",
|
|
86
79
|
fileLines?: readonly string[],
|
|
87
80
|
): string {
|
|
88
|
-
const phrase =
|
|
89
|
-
|
|
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
|
-
`
|
|
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
|
-
"
|
|
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
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
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
|
-
|
|
128
|
-
|
|
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
|
|
134
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
|
127
|
+
"`delete block N` does not take body rows. Remove the body, or use `replace block N:`.";
|
|
146
128
|
|
|
147
|
-
/**
|
|
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
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
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
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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 (
|
|
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
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
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
|
|
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
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
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
|
|
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
|
-
*
|
|
214
|
-
*
|
|
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
|
|
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
|
}
|