pi-hashline-edit-pro 2.6.4 → 2.7.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/README.md +78 -28
- package/index.ts +10 -5
- package/package.json +2 -2
- package/prompts/grep-guidelines.md +6 -0
- package/prompts/grep-snippet.md +1 -0
- package/prompts/grep.md +1 -0
- package/prompts/insert-guidelines.md +6 -0
- package/prompts/insert-snippet.md +1 -0
- package/prompts/insert.md +1 -0
- package/prompts/read-guidelines.md +1 -1
- package/prompts/read.md +1 -1
- package/prompts/replace-guidelines.md +5 -5
- package/prompts/replace-snippet.md +1 -1
- package/prompts/replace.md +1 -1
- package/prompts/undo-last-change-guidelines.md +2 -0
- package/prompts/undo-last-change-snippet.md +1 -0
- package/prompts/undo-last-change.md +1 -0
- package/src/commit.ts +107 -0
- package/src/constants.ts +1 -2
- package/src/file-kind.ts +1 -1
- package/src/file-reader.ts +5 -1
- package/src/grep.ts +323 -0
- package/src/hashline/apply.ts +1 -1
- package/src/hashline/hash.ts +1 -1
- package/src/hashline/index.ts +2 -0
- package/src/hashline/parse.ts +3 -3
- package/src/hashline/resolve.ts +55 -29
- package/src/insert.ts +222 -0
- package/src/read.ts +2 -2
- package/src/replace-diff.ts +47 -6
- package/src/replace-render.ts +117 -2
- package/src/replace-response.ts +12 -6
- package/src/replace-undo.ts +12 -12
- package/src/replace.ts +43 -245
- package/src/validation.ts +1 -1
- package/prompts/undo-last-replace-guidelines.md +0 -2
- package/prompts/undo-last-replace-snippet.md +0 -1
- package/prompts/undo-last-replace.md +0 -1
package/src/replace.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { Markdown, Text } from "@earendil-works/pi-tui";
|
|
2
1
|
import type {
|
|
3
2
|
ExtensionAPI,
|
|
4
3
|
ToolDefinition,
|
|
@@ -8,13 +7,12 @@ import { Type } from "typebox";
|
|
|
8
7
|
import { constants } from "fs";
|
|
9
8
|
import {
|
|
10
9
|
genDiff,
|
|
11
|
-
restoreEndings,
|
|
12
10
|
type LineEnding,
|
|
13
11
|
} from "./replace-diff";
|
|
14
|
-
import { readNormFile,
|
|
12
|
+
import { readNormFile, type NormFile } from "./file-reader";
|
|
15
13
|
import { normReq } from "./replace-normalize";
|
|
16
14
|
import { isRec, rejectUnknownFields, abortIf, makePrepareArguments } from "./utils";
|
|
17
|
-
import { resolveTarget
|
|
15
|
+
import { resolveTarget } from "./fs-write";
|
|
18
16
|
import { applyEdit,
|
|
19
17
|
lineHashes,
|
|
20
18
|
resEdit,
|
|
@@ -26,33 +24,24 @@ import { applyEdit,
|
|
|
26
24
|
type NEdit,
|
|
27
25
|
} from "./hashline";
|
|
28
26
|
import { toCwd } from "./paths";
|
|
27
|
+
import type { RMetrics } from "./replace-response";
|
|
29
28
|
import {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
type RMeta,
|
|
33
|
-
type RMetrics,
|
|
34
|
-
} from "./replace-response";
|
|
35
|
-
import {
|
|
36
|
-
buildAppliedText,
|
|
37
|
-
mkMdTheme,
|
|
38
|
-
fmtCall,
|
|
39
|
-
fmtResultMd,
|
|
40
|
-
getPreviewInput,
|
|
41
|
-
getResultText,
|
|
42
|
-
isApplied,
|
|
29
|
+
makeRenderCall,
|
|
30
|
+
renderEditResult,
|
|
43
31
|
type RPreview,
|
|
44
32
|
type RRState,
|
|
45
33
|
} from "./replace-render";
|
|
34
|
+
export { reuseText, reuseMarkdown } from "./replace-render";
|
|
46
35
|
import { loadP, loadGuide } from "./prompts";
|
|
47
|
-
import { saveUndo } from "./replace-undo";
|
|
48
36
|
import { loadHashStore, findSnapshotPaths, type HashStore } from "./hash-store";
|
|
49
|
-
import { getServed, recordServedSafe
|
|
37
|
+
import { getServed, recordServedSafe } from "./served";
|
|
50
38
|
import { noopPayloadKey, markBoundaryNoop, consumeBoundaryBypass, clearBoundaryBypass } from "./boundary-bypass";
|
|
39
|
+
import { commitEdit } from "./commit";
|
|
51
40
|
|
|
52
41
|
const replacementLinesSchema = Type.Array(
|
|
53
42
|
Type.String({
|
|
54
43
|
description:
|
|
55
|
-
"One replacement line. Each element is exactly one line; do not embed \\n inside an element
|
|
44
|
+
"One replacement line. Each element is exactly one line; do not embed \\n inside an element: use separate elements.",
|
|
56
45
|
}),
|
|
57
46
|
{
|
|
58
47
|
description:
|
|
@@ -61,16 +50,16 @@ const replacementLinesSchema = Type.Array(
|
|
|
61
50
|
);
|
|
62
51
|
|
|
63
52
|
const removeFromSchema = Type.String({
|
|
64
|
-
description: "Bare 3-char HASH only (e.g. \"aB3\")
|
|
53
|
+
description: "Bare 3-char HASH only (e.g. \"aB3\"): copy just the hash from the leftmost column of a read row like `aB3│content`; never the line content. Marks the FIRST line to remove (inclusive)",
|
|
65
54
|
});
|
|
66
55
|
|
|
67
56
|
const removeToSchema = Type.String({
|
|
68
|
-
description: "Bare 3-char HASH only (e.g. \"aB3\")
|
|
57
|
+
description: "Bare 3-char HASH only (e.g. \"aB3\"): copy just the hash from the leftmost column of a read row like `aB3│content`; never the line content. Marks the LAST line to remove (inclusive)",
|
|
69
58
|
});
|
|
70
59
|
|
|
71
60
|
export const editToolSchema = Type.Object(
|
|
72
61
|
{
|
|
73
|
-
path: Type.Optional(Type.String({ description: "Path to edit. Required
|
|
62
|
+
path: Type.Optional(Type.String({ description: "Path to edit. Required: always provide it explicitly; it is only auto-resolved from the anchors as a fallback when omitted by mistake." })),
|
|
74
63
|
remove_from: removeFromSchema,
|
|
75
64
|
remove_to: removeToSchema,
|
|
76
65
|
replacement_lines: replacementLinesSchema,
|
|
@@ -93,7 +82,7 @@ export type ReplaceDetails = {
|
|
|
93
82
|
metrics?: RMetrics;
|
|
94
83
|
};
|
|
95
84
|
|
|
96
|
-
interface PipelineResult {
|
|
85
|
+
export interface PipelineResult {
|
|
97
86
|
path: string;
|
|
98
87
|
originalNormalized: string;
|
|
99
88
|
result: string;
|
|
@@ -109,10 +98,9 @@ interface PipelineResult {
|
|
|
109
98
|
totalAddedLines: number;
|
|
110
99
|
totalRemovedLines: number;
|
|
111
100
|
hadBoundaryDedup: boolean;
|
|
101
|
+
boundaryRemovedLines: number;
|
|
112
102
|
}
|
|
113
103
|
|
|
114
|
-
const PREVIEW_DEBOUNCE_MS = 150;
|
|
115
|
-
|
|
116
104
|
const ROOT_KS = new Set(["path", "remove_from", "remove_to", "replacement_lines"]);
|
|
117
105
|
|
|
118
106
|
export function assertReq(
|
|
@@ -135,7 +123,7 @@ export function assertReq(
|
|
|
135
123
|
request.replacement_lines.some((line) => typeof line !== "string")
|
|
136
124
|
) {
|
|
137
125
|
throw new Error(
|
|
138
|
-
'[E_BAD_SHAPE] Edit request requires "remove_from", "remove_to", and "replacement_lines"
|
|
126
|
+
'[E_BAD_SHAPE] Edit request requires "remove_from", "remove_to", and "replacement_lines" (array of strings, one per line; use [] to delete).',
|
|
139
127
|
);
|
|
140
128
|
}
|
|
141
129
|
}
|
|
@@ -165,12 +153,12 @@ async function resolveMissingPath(
|
|
|
165
153
|
if (matches.length === 1) {
|
|
166
154
|
return {
|
|
167
155
|
path: matches[0]!,
|
|
168
|
-
warning: `[E_BAD_SHAPE] Autocorrected: missing "path" resolved to ${matches[0]}
|
|
156
|
+
warning: `[E_BAD_SHAPE] Autocorrected: missing "path" resolved to ${matches[0]}.`,
|
|
169
157
|
};
|
|
170
158
|
}
|
|
171
159
|
if (matches.length > 1) {
|
|
172
160
|
throw new Error(
|
|
173
|
-
`[E_BAD_SHAPE] Edit request requires a non-empty "path" string; the anchors match multiple known files: ${matches.join(", ")}
|
|
161
|
+
`[E_BAD_SHAPE] Edit request requires a non-empty "path" string; the anchors match multiple known files: ${matches.join(", ")}.`,
|
|
174
162
|
);
|
|
175
163
|
}
|
|
176
164
|
return undefined;
|
|
@@ -182,6 +170,7 @@ export interface ExecPipelineOptions {
|
|
|
182
170
|
store?: HashStore;
|
|
183
171
|
noPersist?: boolean;
|
|
184
172
|
skipBoundaryDedup?: boolean;
|
|
173
|
+
preloadedNorm?: NormFile;
|
|
185
174
|
}
|
|
186
175
|
|
|
187
176
|
function collectRemovedHashes(
|
|
@@ -242,7 +231,7 @@ export async function execPipeline(
|
|
|
242
231
|
|
|
243
232
|
const hashStore = options?.store ?? await loadHashStore();
|
|
244
233
|
const { normalized: originalNormalized, bom, originalEnding, fileHashes: originalHashes, hadUtf8DecodeErrors, absolutePath } = await readNormFile(
|
|
245
|
-
path, cwd, { signal: options?.signal, accessMode: options?.accessMode, maxLines: MAX_HASH_LINES, store: hashStore, noPersist: options?.noPersist },
|
|
234
|
+
path, cwd, { signal: options?.signal, accessMode: options?.accessMode, maxLines: MAX_HASH_LINES, store: hashStore, noPersist: options?.noPersist, preloadedNorm: options?.preloadedNorm },
|
|
246
235
|
);
|
|
247
236
|
|
|
248
237
|
const served = await getServed(hashStore, absolutePath);
|
|
@@ -303,6 +292,7 @@ export async function execPipeline(
|
|
|
303
292
|
totalAddedLines,
|
|
304
293
|
totalRemovedLines,
|
|
305
294
|
hadBoundaryDedup: (anchorResult.autoFixes?.length ?? 0) > 0,
|
|
295
|
+
boundaryRemovedLines: anchorResult.autoFixes?.length ?? 0,
|
|
306
296
|
};
|
|
307
297
|
}
|
|
308
298
|
|
|
@@ -336,21 +326,6 @@ type ToolDef = ToolDefinition<
|
|
|
336
326
|
RRState
|
|
337
327
|
> & { renderShell?: "default" | "self" };
|
|
338
328
|
|
|
339
|
-
export function reuseText(context: any, content: string): Text {
|
|
340
|
-
const t = context.lastComponent instanceof Text
|
|
341
|
-
? context.lastComponent
|
|
342
|
-
: new Text("", 0, 0);
|
|
343
|
-
t.setText(content);
|
|
344
|
-
return t;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
export function reuseMarkdown(context: any, content: string, theme: any): Markdown {
|
|
348
|
-
const m = context.lastComponent instanceof Markdown
|
|
349
|
-
? context.lastComponent
|
|
350
|
-
: new Markdown("", 0, 0, mkMdTheme(theme));
|
|
351
|
-
m.setText(content);
|
|
352
|
-
return m;
|
|
353
|
-
}
|
|
354
329
|
|
|
355
330
|
export function buildToolDef(): ToolDef {
|
|
356
331
|
const E_DESC = loadP("../prompts/replace.md");
|
|
@@ -367,107 +342,17 @@ export function buildToolDef(): ToolDef {
|
|
|
367
342
|
promptGuidelines: E_GUIDE,
|
|
368
343
|
prepareArguments: makePrepareArguments(),
|
|
369
344
|
renderShell: "default",
|
|
370
|
-
renderCall(
|
|
371
|
-
const previewInput = getPreviewInput(args);
|
|
372
|
-
const cancelPendingPreview = () => {
|
|
373
|
-
if (context.state.previewTimer) {
|
|
374
|
-
clearTimeout(context.state.previewTimer);
|
|
375
|
-
context.state.previewTimer = undefined;
|
|
376
|
-
}
|
|
377
|
-
};
|
|
378
|
-
if (context.executionStarted) {
|
|
379
|
-
cancelPendingPreview();
|
|
380
|
-
context.state.argsKey = undefined;
|
|
381
|
-
context.state.preview = undefined;
|
|
382
|
-
context.state.previewGeneration =
|
|
383
|
-
(context.state.previewGeneration ?? 0) + 1;
|
|
384
|
-
} else if (!context.argsComplete || !previewInput) {
|
|
385
|
-
cancelPendingPreview();
|
|
386
|
-
context.state.argsKey = undefined;
|
|
387
|
-
context.state.preview = undefined;
|
|
388
|
-
context.state.previewGeneration =
|
|
389
|
-
(context.state.previewGeneration ?? 0) + 1;
|
|
390
|
-
} else {
|
|
391
|
-
const argsKey = JSON.stringify(previewInput);
|
|
392
|
-
if (context.state.argsKey !== argsKey) {
|
|
393
|
-
cancelPendingPreview();
|
|
394
|
-
context.state.argsKey = argsKey;
|
|
395
|
-
context.state.preview = undefined;
|
|
396
|
-
const previewGeneration = (context.state.previewGeneration ?? 0) + 1;
|
|
397
|
-
context.state.previewGeneration = previewGeneration;
|
|
398
|
-
context.state.previewTimer = setTimeout(() => {
|
|
399
|
-
context.state.previewTimer = undefined;
|
|
400
|
-
compPreview(args, context.cwd)
|
|
401
|
-
.then((preview) => {
|
|
402
|
-
if (
|
|
403
|
-
context.state.argsKey === argsKey &&
|
|
404
|
-
context.state.previewGeneration === previewGeneration
|
|
405
|
-
) {
|
|
406
|
-
context.state.preview = preview;
|
|
407
|
-
context.invalidate();
|
|
408
|
-
}
|
|
409
|
-
})
|
|
410
|
-
.catch((err: unknown) => {
|
|
411
|
-
if (
|
|
412
|
-
context.state.argsKey === argsKey &&
|
|
413
|
-
context.state.previewGeneration === previewGeneration
|
|
414
|
-
) {
|
|
415
|
-
context.state.preview = {
|
|
416
|
-
error: err instanceof Error ? err.message : String(err),
|
|
417
|
-
};
|
|
418
|
-
context.invalidate();
|
|
419
|
-
}
|
|
420
|
-
});
|
|
421
|
-
}, PREVIEW_DEBOUNCE_MS);
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
const text =
|
|
425
|
-
(context.lastComponent as Text | undefined) ?? new Text("", 0, 0);
|
|
426
|
-
text.setText(
|
|
427
|
-
fmtCall(
|
|
428
|
-
getPreviewInput(args) ?? undefined,
|
|
429
|
-
context.state as RRState,
|
|
430
|
-
context.expanded,
|
|
431
|
-
theme,
|
|
432
|
-
),
|
|
433
|
-
);
|
|
434
|
-
return text;
|
|
435
|
-
},
|
|
436
|
-
|
|
345
|
+
renderCall: makeRenderCall(compPreview),
|
|
437
346
|
renderResult(result, { isPartial }, theme, context) {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
const renderState = context.state as RRState | undefined;
|
|
449
|
-
if (renderState) {
|
|
450
|
-
if (renderState.previewTimer) {
|
|
451
|
-
clearTimeout(renderState.previewTimer);
|
|
452
|
-
renderState.previewTimer = undefined;
|
|
453
|
-
}
|
|
454
|
-
renderState.preview = undefined;
|
|
455
|
-
renderState.previewGeneration = (renderState.previewGeneration ?? 0) + 1;
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
if (context.isError) {
|
|
459
|
-
return renderedText
|
|
460
|
-
? reuseText(context, `\n${theme.fg("error", renderedText)}`)
|
|
461
|
-
: new Text("", 0, 0);
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
if (isApplied(typedResult.details)) {
|
|
465
|
-
const appliedText = buildAppliedText(renderedText, typedResult.details, theme);
|
|
466
|
-
return appliedText ? reuseText(context, appliedText) : new Text("", 0, 0);
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
if (!renderedText) return new Text("", 0, 0);
|
|
470
|
-
return reuseMarkdown(context, fmtResultMd(renderedText), theme);
|
|
347
|
+
return renderEditResult(
|
|
348
|
+
result as {
|
|
349
|
+
content?: Array<{ type: string; text?: string }>;
|
|
350
|
+
details?: ReplaceDetails;
|
|
351
|
+
},
|
|
352
|
+
isPartial,
|
|
353
|
+
theme,
|
|
354
|
+
context,
|
|
355
|
+
);
|
|
471
356
|
},
|
|
472
357
|
|
|
473
358
|
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
@@ -486,111 +371,24 @@ export function buildToolDef(): ToolDef {
|
|
|
486
371
|
const boundaryBypass = consumeBoundaryBypass(mutationTargetPath, noopPayload);
|
|
487
372
|
return withFileMutationQueue(mutationTargetPath, async () => {
|
|
488
373
|
abortIf(signal);
|
|
489
|
-
|
|
490
|
-
const {
|
|
491
|
-
originalNormalized,
|
|
492
|
-
originalHashes,
|
|
493
|
-
result,
|
|
494
|
-
bom,
|
|
495
|
-
originalEnding,
|
|
496
|
-
hadUtf8DecodeErrors,
|
|
497
|
-
warnings,
|
|
498
|
-
noopEdit,
|
|
499
|
-
firstChangedLine,
|
|
500
|
-
lastChangedLine,
|
|
501
|
-
resultHashes,
|
|
502
|
-
hadBoundaryDedup,
|
|
503
|
-
totalAddedLines,
|
|
504
|
-
totalRemovedLines,
|
|
505
|
-
} = await execPipeline(
|
|
374
|
+
const pipe = await execPipeline(
|
|
506
375
|
normalizedParams,
|
|
507
376
|
ctx.cwd,
|
|
508
377
|
{ accessMode: constants.R_OK | constants.W_OK, signal, skipBoundaryDedup: boundaryBypass },
|
|
509
378
|
);
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
if (boundaryBypass && originalNormalized !== result) {
|
|
515
|
-
warnings.push("[E_BOUNDARY_BYPASS] Boundary dedup was off for this call. Boundary dedup is now restored.");
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
const editsAttempted = 1;
|
|
519
|
-
if (originalNormalized === result) {
|
|
520
|
-
const noopSnapshotId = await safeSnapId(absolutePath, "noop edit");
|
|
521
|
-
if (hadBoundaryDedup) {
|
|
522
|
-
markBoundaryNoop(mutationTargetPath, noopPayload);
|
|
523
|
-
}
|
|
524
|
-
return buildNoop({
|
|
525
|
-
path,
|
|
526
|
-
noopEdit,
|
|
527
|
-
snapshotId: noopSnapshotId,
|
|
528
|
-
editMeta: {
|
|
529
|
-
editsAttempted,
|
|
530
|
-
noopEditsCount: noopEdit ? 1 : 0,
|
|
531
|
-
addedLines: 0,
|
|
532
|
-
removedLines: 0,
|
|
533
|
-
},
|
|
534
|
-
warnings,
|
|
535
|
-
});
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
if (hadUtf8DecodeErrors) {
|
|
539
|
-
warnings.push(
|
|
540
|
-
"Non-UTF-8 bytes were shown as U+FFFD; this edit rewrote the file as UTF-8.",
|
|
541
|
-
);
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
abortIf(signal);
|
|
545
|
-
const undo = await saveUndo(mutationTargetPath, {
|
|
546
|
-
content: originalNormalized,
|
|
547
|
-
bom,
|
|
548
|
-
originalEnding,
|
|
549
|
-
hashes: originalHashes,
|
|
550
|
-
resultContent: result,
|
|
551
|
-
});
|
|
552
|
-
if (!undo.persisted) {
|
|
553
|
-
throw new Error(
|
|
554
|
-
`[E_UNDO_UNAVAILABLE] Cannot persist undo history to the hash store; the edit was NOT applied and ${path} is unchanged. Retry the replace, or use write if the store cannot be recovered.`
|
|
555
|
-
);
|
|
556
|
-
}
|
|
557
|
-
try {
|
|
558
|
-
abortIf(signal);
|
|
559
|
-
await writeAtomic(
|
|
560
|
-
absolutePath,
|
|
561
|
-
bom + restoreEndings(result, originalEnding),
|
|
562
|
-
);
|
|
563
|
-
} catch (error) {
|
|
564
|
-
await undo.restore();
|
|
565
|
-
throw error;
|
|
566
|
-
}
|
|
567
|
-
clearBoundaryBypass(mutationTargetPath);
|
|
568
|
-
const updatedSnapshotId = await safeSnapId(absolutePath, "post-edit");
|
|
569
|
-
|
|
570
|
-
const editMeta: RMeta = {
|
|
571
|
-
editsAttempted,
|
|
572
|
-
noopEditsCount: noopEdit ? 1 : 0,
|
|
573
|
-
firstChangedLine,
|
|
574
|
-
lastChangedLine,
|
|
575
|
-
addedLines: totalAddedLines,
|
|
576
|
-
removedLines: totalRemovedLines,
|
|
577
|
-
};
|
|
578
|
-
|
|
579
|
-
const successInput = {
|
|
379
|
+
const appliedWarnings = boundaryBypass
|
|
380
|
+
? ["[E_BOUNDARY_BYPASS] Boundary dedup was off for this call and is back on."]
|
|
381
|
+
: [];
|
|
382
|
+
return commitEdit(pipe, {
|
|
580
383
|
path,
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
};
|
|
589
|
-
const changed = buildChanged(successInput);
|
|
590
|
-
if (changed.details.diff) {
|
|
591
|
-
await recordServedDiffSafe(mutationTargetPath, changed.details.diff, "post-edit diff", new Set(resultHashes));
|
|
592
|
-
}
|
|
593
|
-
return changed;
|
|
384
|
+
absolutePath,
|
|
385
|
+
mutationTargetPath,
|
|
386
|
+
signal,
|
|
387
|
+
prefixWarnings: resolution ? [resolution.warning] : [],
|
|
388
|
+
appliedWarnings,
|
|
389
|
+
onApplied: () => clearBoundaryBypass(mutationTargetPath),
|
|
390
|
+
onNoopDedup: () => markBoundaryNoop(mutationTargetPath, noopPayload),
|
|
391
|
+
});
|
|
594
392
|
});
|
|
595
393
|
},
|
|
596
394
|
};
|
package/src/validation.ts
CHANGED
|
@@ -38,7 +38,7 @@ export function valKind(file: LFile, path: string): asserts file is { kind: "tex
|
|
|
38
38
|
}
|
|
39
39
|
if (file.kind === "too_large") {
|
|
40
40
|
throw new Error(
|
|
41
|
-
`[E_FILE_TOO_LARGE] File is too large: ${path} (${file.description}).
|
|
41
|
+
`[E_FILE_TOO_LARGE] File is too large: ${path} (${file.description}). For very large files, use write.`,
|
|
42
42
|
);
|
|
43
43
|
}
|
|
44
44
|
}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
- `undo_last_replace`: reverts only the most recent replace on the file — any write to the file clears the undo history, so call it immediately after a bad replace. A replace is bad when its post-edit diff shows `-HASH│` rows for lines you meant to keep (a closing brace, import, or declaration).
|
|
2
|
-
- `undo_last_replace`: when auto-read shows the post-edit diff, its `+HASH│` and ` HASH│` rows are the fresh anchors for the restored file, so follow-up edits can anchor on the diff without re-reading.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
Undo the last replace on a file
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
Undo the last replace on a file, reverting it to its previous state. Use when a replace produced incorrect results (e.g., wrong content, duplicated lines, broken syntax).
|