pi-hashline-edit-pro 4.5.0 → 4.5.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/README.md +6 -6
- package/index.ts +6 -3
- package/package.json +1 -1
- package/prompts/insert.md +1 -1
- package/prompts/replace.md +1 -1
- package/src/insert.ts +3 -3
- package/src/payload-contract.ts +1 -1
- package/src/replace-render.ts +11 -3
- package/src/replace-response.ts +23 -8
- package/src/replace.ts +3 -2
- package/src/utils.ts +2 -2
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ the result is the post-edit diff with fresh anchors, so the next edit needs no r
|
|
|
41
41
|
- [Configuration](#configuration)
|
|
42
42
|
- [Limits](#limits)
|
|
43
43
|
- [Tool result details](#tool-result-details)
|
|
44
|
-
- [Error and
|
|
44
|
+
- [Error, warning, and hint codes](#error-warning-and-hint-codes)
|
|
45
45
|
- [Troubleshooting](#troubleshooting)
|
|
46
46
|
- [Privacy and on-disk state](#privacy-and-on-disk-state)
|
|
47
47
|
- [How anchors work](#how-anchors-work)
|
|
@@ -390,16 +390,16 @@ All seven tools return machine-readable metadata in `details` alongside the mode
|
|
|
390
390
|
| Tool | `details` |
|
|
391
391
|
| --- | --- |
|
|
392
392
|
| `read` | `truncation` (set when output was truncated), `snapshotId` (a `v2\|path\|ino\|mtime\|ctime\|size` fingerprint), `nextOffset` (use as the next `offset`), and `metrics` with `truncated` and `next_offset`. |
|
|
393
|
-
| `replace`, `insert` | `diff` (post-edit diff, capped, with current anchors on `+anchor│` and ` anchor│` rows; a same-message batch reports the combined diff on its last call and an empty diff on earlier calls), `patch` (a standard unified patch for external tools, capped like the diff), `patchTruncated` (true when the patch was cut or skipped for a pair over 1MB and can no longer be applied as-is), `firstChangedLine`, `snapshotId`, `classification` (`"noop"` when nothing changed), `batch` (`{ id, size, last, total }` marking same-message batch membership; earlier members also carry `aborted: true` and `abortMessage` after a batch abort), and `metrics`: `edits_attempted`, `edits_noop`, `warnings`, `classification` (`"applied"` or `"noop"`), `changed_lines` (`{ first, last }`), `added_lines`, `removed_lines`. |
|
|
393
|
+
| `replace`, `insert` | `diff` (post-edit diff, capped, with current anchors on `+anchor│` and ` anchor│` rows; a same-message batch reports the combined diff on its last call and an empty diff on earlier calls), `patch` (a standard unified patch for external tools, capped like the diff), `patchTruncated` (true when the patch was cut or skipped for a pair over 1MB and can no longer be applied as-is), `firstChangedLine`, `snapshotId`, `classification` (`"noop"` when nothing changed), `batch` (`{ id, size, last, total }` marking same-message batch membership; earlier members also carry `aborted: true` and `abortMessage` after a batch abort), `hints` (informative `[H_*]` notices, for example literal escaped text written as sent), and `metrics`: `edits_attempted`, `edits_noop`, `warnings`, `classification` (`"applied"` or `"noop"`), `changed_lines` (`{ first, last }`), `added_lines`, `removed_lines`. |
|
|
394
394
|
| `copy`, `move` | Same shape as `replace`: `diff` (post-edit diff with current anchors), `patch`, `patchTruncated`, `firstChangedLine`, `snapshotId`, `classification` (`"noop"` when a move changes nothing), and `metrics` with the same counters. |
|
|
395
395
|
| `undo_last_change` | `diff` (the undo diff with restored anchors), `patch`, `patchTruncated`, and `metrics` in the same shape as `replace`. |
|
|
396
396
|
| `anchor_grep` | `metrics` with `matches` (capped at `limit`), `files`, and `truncated`; `truncation` (the standard pi truncation report) when output was cut; and `linesTruncated` (true when long lines were shown as fragments). |
|
|
397
397
|
|
|
398
|
-
`snapshotId`, `firstChangedLine`, and `metrics.warnings` are the three fields external consumers most often read; `snapshotId` is a string fingerprint, `firstChangedLine` is a 1-based line number on the result file, and `metrics.warnings` counts the `[W_*]` notices in `details.warnings
|
|
398
|
+
`snapshotId`, `firstChangedLine`, and `metrics.warnings` are the three fields external consumers most often read; `snapshotId` is a string fingerprint, `firstChangedLine` is a 1-based line number on the result file, and `metrics.warnings` counts the `[W_*]` notices in `details.warnings`; hint `[H_*]` notices live in `details.hints` and are not counted.
|
|
399
399
|
|
|
400
|
-
## Error and
|
|
400
|
+
## Error, warning, and hint codes
|
|
401
401
|
|
|
402
|
-
Codes starting with `E_` are errors: nothing was written, with one exception. `File was written; anchor finalization failed` means the file was written and one undo reverts it. Codes starting with `W_` are warnings: the call succeeded with an auto-fix notice or an anchor-reclaim notice; check `classification` (`applied` vs `noop`) in `details.metrics` to tell whether bytes changed. `[E_AUTO_READ_ALL]` is informational rather than a failure: the `read` was refused because the file is unchanged since the start-of-session auto-read, so the attached content is still exact.
|
|
402
|
+
Codes starting with `E_` are errors: nothing was written, with one exception. `File was written; anchor finalization failed` means the file was written and one undo reverts it. Codes starting with `W_` are warnings: the call succeeded with an auto-fix notice or an anchor-reclaim notice; check `classification` (`applied` vs `noop`) in `details.metrics` to tell whether bytes changed. Codes starting with `H_` are hints: the call succeeded and the file holds exactly what was requested, so the notice is informational, never blocks an edit (not even in strict-input mode), and is reported in `details.hints` instead of `details.warnings`. `[E_AUTO_READ_ALL]` is informational rather than a failure: the `read` was refused because the file is unchanged since the start-of-session auto-read, so the attached content is still exact.
|
|
403
403
|
|
|
404
404
|
Most common, with the fix:
|
|
405
405
|
|
|
@@ -422,8 +422,8 @@ Full reference:
|
|
|
422
422
|
| `[E_STALE_ANCHOR]` | An anchor is not owned in this session (it was never shown to you, or its line was edited or the file was rewritten); call `read` for fresh anchors. |
|
|
423
423
|
| `[W_INVALID_PATCH]` | A `replacement_lines` element is a diff-preview row (`+anchor│`, `-anchor│`, `- │`). The marker is stripped automatically with a warning. |
|
|
424
424
|
| `[W_BARE_HASH_PREFIX]` | A `replacement_lines` element starts with an `anchor│` prefix. The prefix is stripped automatically with a warning. |
|
|
425
|
-
| `[W_LITERAL_ESCAPE]` | `lines` or `replacement_lines` contains literal escape text such as `\u200b` or `\n`; the file receives those characters as written. Decode the escapes first if the text came from a quoted prompt. |
|
|
426
425
|
| `[W_ANCHOR_RECLAIMED]` | The session's anchor quota was exhausted, so all anchors of the listed files (the least recently read or edited) were freed to make room. Read those files again before editing them. |
|
|
426
|
+
| `[H_LITERAL_ESCAPE]` | `lines` or `replacement_lines` contains the literal escaped text such as `\u200b` or `\n`; the file receives those characters as written. Decode the escapes first if the text came from a quoted prompt. |
|
|
427
427
|
| `[E_WOULD_EMPTY]` | An edit would empty a non-empty file; use `write` instead. A cross-file `move` may empty its source file. |
|
|
428
428
|
| `[E_NOT_FOUND]` | The path does not exist. |
|
|
429
429
|
| `[E_ACCESS]` | The file is not readable or writable. |
|
package/index.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { buildAutoReadAllInjection, autoReadAllBudget } from "./src/auto-read-al
|
|
|
11
11
|
import { clearAutoReadAllComplete } from "./src/auto-read-all-state";
|
|
12
12
|
import type { RMetrics } from "./src/replace-response";
|
|
13
13
|
import type { ReplaceDetails } from "./src/replace";
|
|
14
|
-
import { extractWarnings } from "./src/replace-render";
|
|
14
|
+
import { extractHints, extractWarnings } from "./src/replace-render";
|
|
15
15
|
import { MAX_HASH_LINES } from "./src/hashline";
|
|
16
16
|
import type { AutoReadAllMode } from "./src/config";
|
|
17
17
|
import {
|
|
@@ -266,6 +266,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
266
266
|
const toolDetails = event.details as ReplaceDetails | undefined;
|
|
267
267
|
const diff = toolDetails?.diff;
|
|
268
268
|
const detailWarnings = Array.isArray(toolDetails?.warnings) ? toolDetails.warnings.filter((w): w is string => typeof w === "string") : [];
|
|
269
|
+
const detailHints = Array.isArray(toolDetails?.hints) ? toolDetails.hints.filter((h): h is string => typeof h === "string") : [];
|
|
269
270
|
if (typeof diff !== "string") return;
|
|
270
271
|
const hasDiff = diff.length > 0;
|
|
271
272
|
|
|
@@ -277,12 +278,14 @@ export default function (pi: ExtensionAPI): void {
|
|
|
277
278
|
.map((entry) => entry.text)
|
|
278
279
|
.join("\n");
|
|
279
280
|
const warnings = detailWarnings.length ? `Warnings:\n${detailWarnings.join("\n")}` : extractWarnings(rendered);
|
|
280
|
-
const
|
|
281
|
+
const hints = detailHints.length ? `Hints:\n${detailHints.join("\n")}` : extractHints(rendered);
|
|
282
|
+
const notices = [warnings, hints].filter((part): part is string => part !== undefined).join("\n\n");
|
|
283
|
+
const noticeText = hasDiff ? (notices ? `${diff}\n\n${notices}` : diff) : notices ? `[post-edit] applied successfully; the diff is empty (whitespace-only change).\n\n${notices}` : "[post-edit] applied successfully; the diff is empty (whitespace-only change).";
|
|
281
284
|
return {
|
|
282
285
|
content: [
|
|
283
286
|
{
|
|
284
287
|
type: "text",
|
|
285
|
-
text:
|
|
288
|
+
text: noticeText,
|
|
286
289
|
},
|
|
287
290
|
],
|
|
288
291
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-hashline-edit-pro",
|
|
3
|
-
"version": "4.5.
|
|
3
|
+
"version": "4.5.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Hash-anchored read/replace/insert/grep tools for pi-coding-agent. Every line gets a unique 4-char tokenizer-friendly anchor that stays stable across edits; stale or ambiguous anchors are rejected, never fuzzy-matched. Undo persists across restarts.",
|
|
6
6
|
"main": "index.ts",
|
package/prompts/insert.md
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
Insert lines after or before one existing line in a text file, addressed by a bare anchor from any served anchor│content row. The anchor line is preserved: `lines` go after it with `direction: "after"` or before it with `direction: "before"`, one string per line, no anchor prefixes. An element is one line: a real line-break character (`\n`, `\r\n`, or `\r`) splits it and sets that line's ending
|
|
1
|
+
Insert lines after or before one existing line in a text file, addressed by a bare anchor from any served anchor│content row. The anchor line is preserved: `lines` go after it with `direction: "after"` or before it with `direction: "before"`, one string per line, no anchor prefixes. An element is one line: a real line-break character (`\n`, `\r\n`, or `\r`) splits it and sets that line's ending. Inserted lines are written exactly as given; nothing else in the file changes.
|
|
2
2
|
|
|
3
3
|
Same-file calls in one message batch: earlier calls reply `In batch N` and the last call shows the combined diff, with one undo for the whole batch.
|
package/prompts/replace.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Replace a range of lines (or a single line) in a text file, targeted by 4-character anchors from any served anchor│content row. Give `remove_from` and `remove_to` as bare anchors marking the first and last line to remove, and `replacement_lines` as one string per new line with no anchor prefixes. An element is one line: a real line-break character (`\n`, `\r\n`, or `\r`) splits it and sets that line's ending
|
|
1
|
+
Replace a range of lines (or a single line) in a text file, targeted by 4-character anchors from any served anchor│content row. Give `remove_from` and `remove_to` as bare anchors marking the first and last line to remove, and `replacement_lines` as one string per new line with no anchor prefixes. An element is one line: a real line-break character (`\n`, `\r\n`, or `\r`) splits it and sets that line's ending. `[]` deletes the range.
|
|
2
2
|
|
|
3
3
|
Same-file calls in one message batch: earlier calls reply `In batch N` and the last call shows the combined diff, with one undo for the whole batch.
|
|
4
4
|
|
package/src/insert.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { stripAnchorRow } from "./hashline/resolve";
|
|
|
11
11
|
import { withAnchorSession } from "./anchor-registry";
|
|
12
12
|
import { loadP, loadGuide } from "./prompts";
|
|
13
13
|
import { assertInsertReq, normReq, type InsertReq } from "./payload-contract";
|
|
14
|
-
import { decodeStringArray, isRec,
|
|
14
|
+
import { decodeStringArray, isRec, literalEscapeHint, splitLines } from "./utils";
|
|
15
15
|
import { queuedEdit, editToolBase, editRenderCallWrapper, editRenderResultWrapper, resolveEditTargetWithRequirement, throwIfStrictInput, withInsertPrompts, DEFAULT_EDIT_FLAGS, type EditToolFlags } from "./edit-common";
|
|
16
16
|
import type { RPreview, RRState } from "./replace-render";
|
|
17
17
|
export { assertInsertReq, type InsertReq };
|
|
@@ -28,7 +28,7 @@ const insertDirectionSchema = Type.Union(
|
|
|
28
28
|
|
|
29
29
|
const insertLinesSchema = Type.Array(
|
|
30
30
|
Type.String({
|
|
31
|
-
description: "One line to insert. A real line break (\\n, \\r\\n, or \\r) splits it into lines and sets their endings
|
|
31
|
+
description: "One line to insert. A real line break (\\n, \\r\\n, or \\r) splits it into lines and sets their endings.",
|
|
32
32
|
}),
|
|
33
33
|
{
|
|
34
34
|
description: 'One string per line; [""] is a blank line; never include the anchor line.',
|
|
@@ -192,7 +192,7 @@ export function buildInsertToolDef(flags: EditToolFlags = DEFAULT_EDIT_FLAGS): I
|
|
|
192
192
|
}
|
|
193
193
|
assertInsertReq(canonical);
|
|
194
194
|
const req = canonical;
|
|
195
|
-
const literalEscape =
|
|
195
|
+
const literalEscape = literalEscapeHint(req.lines, "lines");
|
|
196
196
|
if (literalEscape !== undefined) insertWarnings.push(literalEscape);
|
|
197
197
|
const targetPath = await resolveEditTargetWithRequirement({
|
|
198
198
|
anchor: req.anchor,
|
package/src/payload-contract.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { isRec, normalizeRequest, rejectUnknownFields, assertNoNul } from "./uti
|
|
|
4
4
|
const replacementLinesSchema = Type.Array(
|
|
5
5
|
Type.String({
|
|
6
6
|
description:
|
|
7
|
-
"One replacement line. A real line break (\\n, \\r\\n, or \\r) splits it into lines and sets their endings
|
|
7
|
+
"One replacement line. A real line break (\\n, \\r\\n, or \\r) splits it into lines and sets their endings.",
|
|
8
8
|
}),
|
|
9
9
|
{
|
|
10
10
|
description:
|
package/src/replace-render.ts
CHANGED
|
@@ -126,7 +126,13 @@ export function getResultText(result: {
|
|
|
126
126
|
export function extractWarnings(
|
|
127
127
|
text: string | undefined,
|
|
128
128
|
): string | undefined {
|
|
129
|
-
return text?.match(/(?:^|\n)Warnings:\n[\s\S]
|
|
129
|
+
return text?.match(/(?:^|\n)Warnings:\n[\s\S]*?(?=\n\nHints:\n|$)/)?.[0]?.trimStart();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function extractHints(
|
|
133
|
+
text: string | undefined,
|
|
134
|
+
): string | undefined {
|
|
135
|
+
return text?.match(/(?:^|\n)Hints:\n[\s\S]*$/)?.[0]?.trimStart();
|
|
130
136
|
}
|
|
131
137
|
|
|
132
138
|
export function isApplied(
|
|
@@ -153,8 +159,8 @@ export function expandHint(): string {
|
|
|
153
159
|
function extractSummary(text: string | undefined): string | undefined {
|
|
154
160
|
if (!text) return undefined;
|
|
155
161
|
if (text.includes("│")) return undefined;
|
|
156
|
-
const
|
|
157
|
-
const summary =
|
|
162
|
+
const noticesIdx = text.search(/\n\n(?:Warnings|Hints):\n/);
|
|
163
|
+
const summary = noticesIdx >= 0 ? text.slice(0, noticesIdx) : text;
|
|
158
164
|
return summary.length > 0 ? summary : undefined;
|
|
159
165
|
}
|
|
160
166
|
|
|
@@ -190,6 +196,8 @@ export function buildAppliedText(
|
|
|
190
196
|
}
|
|
191
197
|
const warnings = details?.warnings?.length ? `Warnings:\n${details.warnings.join("\n")}` : extractWarnings(text);
|
|
192
198
|
if (warnings) sections.push(warnings);
|
|
199
|
+
const hints = details?.hints?.length ? `Hints:\n${details.hints.join("\n")}` : extractHints(text);
|
|
200
|
+
if (hints) sections.push(hints);
|
|
193
201
|
return sections.length > 0 ? sections.join("\n\n") : undefined;
|
|
194
202
|
}
|
|
195
203
|
|
package/src/replace-response.ts
CHANGED
|
@@ -85,6 +85,17 @@ function warnBlock(warnings: string[] | undefined): string {
|
|
|
85
85
|
return warnings?.length ? `\n\nWarnings:\n${warnings.join("\n")}` : "";
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
function hintBlock(hints: string[] | undefined): string {
|
|
89
|
+
return hints?.length ? `\n\nHints:\n${hints.join("\n")}` : "";
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function splitNotices(notices: string[] | undefined): { warnings: string[]; hints: string[] } {
|
|
93
|
+
const warnings: string[] = [];
|
|
94
|
+
const hints: string[] = [];
|
|
95
|
+
for (const notice of notices ?? []) (notice.startsWith("[H_") ? hints : warnings).push(notice);
|
|
96
|
+
return { warnings, hints };
|
|
97
|
+
}
|
|
98
|
+
|
|
88
99
|
export function buildNoop(input: NoopInput, noopNoun = "Replacement"): TResult {
|
|
89
100
|
const {
|
|
90
101
|
path,
|
|
@@ -97,13 +108,14 @@ export function buildNoop(input: NoopInput, noopNoun = "Replacement"): TResult {
|
|
|
97
108
|
const noopDetailsText = noopEdit
|
|
98
109
|
? `${noopNoun} for ${noopEdit.loc} is identical to current content:\n ${noopEdit.loc}: ${clipLine(noopEdit.currentContent)}`
|
|
99
110
|
: "The edit produced identical content.";
|
|
100
|
-
const
|
|
111
|
+
const { warnings: noticeWarnings, hints } = splitNotices(warnings);
|
|
112
|
+
const text = `No changes made to ${path}\nClassification: noop\n${noopDetailsText}${warnBlock(noticeWarnings)}${hintBlock(hints)}`;
|
|
101
113
|
|
|
102
114
|
const metrics = buildMetrics({
|
|
103
115
|
classification: "noop",
|
|
104
116
|
editsAttempted: editMeta.editsAttempted,
|
|
105
117
|
noopEditsCount: editMeta.noopEditsCount,
|
|
106
|
-
warningsCount:
|
|
118
|
+
warningsCount: noticeWarnings.length,
|
|
107
119
|
});
|
|
108
120
|
|
|
109
121
|
return {
|
|
@@ -115,7 +127,8 @@ export function buildNoop(input: NoopInput, noopNoun = "Replacement"): TResult {
|
|
|
115
127
|
snapshotId,
|
|
116
128
|
classification: "noop" as const,
|
|
117
129
|
metrics,
|
|
118
|
-
...(
|
|
130
|
+
...(noticeWarnings.length ? { warnings: [...noticeWarnings] } : {}),
|
|
131
|
+
...(hints.length ? { hints: [...hints] } : {}),
|
|
119
132
|
},
|
|
120
133
|
};
|
|
121
134
|
}
|
|
@@ -126,22 +139,23 @@ export function buildChanged(input: SuccessInput, verb = "replaced", diffContext
|
|
|
126
139
|
const diffResult = genDiff(originalNormalized, result, diffContextLines, resultHashes, originalHashes, undefined, spans);
|
|
127
140
|
const addedLines = editMeta.addedLines;
|
|
128
141
|
const removedLines = editMeta.removedLines;
|
|
129
|
-
const
|
|
142
|
+
const { warnings: noticeWarnings, hints } = splitNotices(warnings);
|
|
143
|
+
const noticesBlock = `${warnBlock(noticeWarnings)}${hintBlock(hints)}`;
|
|
130
144
|
const successPrefix = `Successfully ${verb} in ${path}.`;
|
|
131
145
|
const lineSummary = addedLines > 0 || removedLines > 0
|
|
132
146
|
? ` Added ${addedLines} line(s), removed ${removedLines} line(s).`
|
|
133
147
|
: "";
|
|
134
148
|
const text = resultLines.length === 0
|
|
135
149
|
? "File is empty. Use replace to insert content."
|
|
136
|
-
:
|
|
137
|
-
? `${successPrefix}${lineSummary}${
|
|
150
|
+
: noticesBlock
|
|
151
|
+
? `${successPrefix}${lineSummary}${noticesBlock}`
|
|
138
152
|
: `${successPrefix}${lineSummary}`;
|
|
139
153
|
|
|
140
154
|
const metrics = buildMetrics({
|
|
141
155
|
classification: "applied",
|
|
142
156
|
editsAttempted: editMeta.editsAttempted,
|
|
143
157
|
noopEditsCount: editMeta.noopEditsCount,
|
|
144
|
-
warningsCount:
|
|
158
|
+
warningsCount: noticeWarnings.length,
|
|
145
159
|
firstChangedLine: editMeta.firstChangedLine,
|
|
146
160
|
lastChangedLine: editMeta.lastChangedLine,
|
|
147
161
|
addedLines,
|
|
@@ -160,7 +174,8 @@ export function buildChanged(input: SuccessInput, verb = "replaced", diffContext
|
|
|
160
174
|
snapshotId,
|
|
161
175
|
metrics,
|
|
162
176
|
diffLineNumbers: diffResult.lineNumbers.map((line) => line ?? null),
|
|
163
|
-
...(
|
|
177
|
+
...(noticeWarnings.length ? { warnings: [...noticeWarnings] } : {}),
|
|
178
|
+
...(hints.length ? { hints: [...hints] } : {}),
|
|
164
179
|
},
|
|
165
180
|
};
|
|
166
181
|
}
|
package/src/replace.ts
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from "./replace-diff";
|
|
11
11
|
import { readNormFile, type NormFile } from "./file-reader";
|
|
12
12
|
import { editToolSchema, buildEditToolSchema, type ReqParams, assertReq, normReq } from "./payload-contract";
|
|
13
|
-
import {
|
|
13
|
+
import { literalEscapeHint, splitLines } from "./utils";
|
|
14
14
|
import { loadP, loadGuide } from "./prompts";
|
|
15
15
|
import { type FileIdentity } from "./fs-write";
|
|
16
16
|
import { applyEdit,
|
|
@@ -48,6 +48,7 @@ export type ReplaceDetails = {
|
|
|
48
48
|
metrics?: RMetrics;
|
|
49
49
|
diffLineNumbers?: (number | null)[];
|
|
50
50
|
warnings?: string[];
|
|
51
|
+
hints?: string[];
|
|
51
52
|
batch?: { id: number; size: number; last: boolean; total: number; aborted?: boolean; abortMessage?: string };
|
|
52
53
|
};
|
|
53
54
|
|
|
@@ -273,7 +274,7 @@ export function buildToolDef(flags: EditToolFlags = DEFAULT_EDIT_FLAGS): ToolDef
|
|
|
273
274
|
const canonical = normReq(params);
|
|
274
275
|
assertReq(canonical);
|
|
275
276
|
const normalizedParams = canonical;
|
|
276
|
-
const literalEscape =
|
|
277
|
+
const literalEscape = literalEscapeHint(normalizedParams.replacement_lines, "replacement_lines");
|
|
277
278
|
const targetPath = await resolveEditTargetWithRequirement({
|
|
278
279
|
removeFrom: normalizedParams.remove_from,
|
|
279
280
|
removeTo: normalizedParams.remove_to,
|
package/src/utils.ts
CHANGED
|
@@ -384,7 +384,7 @@ function isSurrogateEscapePair(line: string, index: number, hex: string): boolea
|
|
|
384
384
|
return false;
|
|
385
385
|
}
|
|
386
386
|
|
|
387
|
-
export function
|
|
387
|
+
export function literalEscapeHint(lines: string[], label: string): string | undefined {
|
|
388
388
|
for (const line of lines) {
|
|
389
389
|
if (!line.includes("\\")) continue;
|
|
390
390
|
const hasRealBreak = REAL_LINE_BREAK_RE.test(line);
|
|
@@ -396,7 +396,7 @@ export function literalEscapeWarning(lines: string[], label: string): string | u
|
|
|
396
396
|
if (hex.toLowerCase() === "dddd") continue;
|
|
397
397
|
if (isSurrogateEscapePair(line, match.index, hex)) continue;
|
|
398
398
|
}
|
|
399
|
-
return `[
|
|
399
|
+
return `[H_LITERAL_ESCAPE] "${label}" contains the literal escaped text "${match[0]}"`;
|
|
400
400
|
}
|
|
401
401
|
}
|
|
402
402
|
return undefined;
|