pi-hashline-edit-pro 2.6.5 → 2.7.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 +70 -18
- package/index.ts +15 -9
- 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/undo-last-change-guidelines.md +3 -0
- package/prompts/undo-last-change-snippet.md +1 -0
- package/prompts/undo-last-change.md +1 -0
- package/src/boundary-bypass.ts +2 -10
- package/src/commit.ts +107 -0
- package/src/file-reader.ts +4 -0
- package/src/grep.ts +323 -0
- package/src/hash-store.ts +40 -12
- package/src/hashline/apply.ts +2 -15
- package/src/hashline/hash.ts +24 -0
- package/src/hashline/index.ts +3 -0
- package/src/hashline/resolve.ts +68 -38
- package/src/insert.ts +222 -0
- package/src/replace-diff.ts +47 -6
- package/src/replace-render.ts +117 -2
- package/src/replace-response.ts +7 -11
- package/src/replace-undo.ts +22 -31
- package/src/replace.ts +35 -242
- package/src/served.ts +2 -3
- 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-undo.ts
CHANGED
|
@@ -85,13 +85,13 @@ export async function clearUndo(path: string): Promise<void> {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
export function
|
|
88
|
+
export function regUndo(pi: ExtensionAPI): void {
|
|
89
89
|
pi.registerTool({
|
|
90
|
-
name: "
|
|
91
|
-
label: "Undo Last
|
|
92
|
-
description: loadP("../prompts/undo-last-
|
|
93
|
-
promptSnippet: loadP("../prompts/undo-last-
|
|
94
|
-
promptGuidelines: loadGuide("../prompts/undo-last-
|
|
90
|
+
name: "undo_last_change",
|
|
91
|
+
label: "Undo Last Change",
|
|
92
|
+
description: loadP("../prompts/undo-last-change.md"),
|
|
93
|
+
promptSnippet: loadP("../prompts/undo-last-change-snippet.md"),
|
|
94
|
+
promptGuidelines: loadGuide("../prompts/undo-last-change-guidelines.md"),
|
|
95
95
|
prepareArguments: makePrepareArguments(),
|
|
96
96
|
parameters: Type.Object({
|
|
97
97
|
path: Type.String({
|
|
@@ -126,26 +126,15 @@ export function regReplaceUndo(pi: ExtensionAPI): void {
|
|
|
126
126
|
if (errCode(error) !== "ENOENT") throw error;
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
if (
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
{
|
|
134
|
-
type: "text",
|
|
135
|
-
text: `[E_UNDO_STALE] Cannot undo last replace on ${path}: the file no longer exists.`
|
|
136
|
-
},
|
|
137
|
-
],
|
|
138
|
-
isError: true,
|
|
139
|
-
details: {},
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
if (currentRaw !== undo.bom + restoreEndings(undo.resultContent, undo.originalEnding)) {
|
|
143
|
-
await clearUndo(mutationTargetPath);
|
|
129
|
+
if (
|
|
130
|
+
currentRaw !== undefined &&
|
|
131
|
+
currentRaw !== undo.bom + restoreEndings(undo.resultContent, undo.originalEnding)
|
|
132
|
+
) {
|
|
144
133
|
return {
|
|
145
134
|
content: [
|
|
146
135
|
{
|
|
147
136
|
type: "text",
|
|
148
|
-
text: `[E_UNDO_STALE] Cannot undo last
|
|
137
|
+
text: `[E_UNDO_STALE] Cannot undo last change on ${path}: the file changed after the edit. The undo record is kept; once the file matches the edited state again, undo_last_change will succeed. Call read() to inspect the current state.`
|
|
149
138
|
},
|
|
150
139
|
],
|
|
151
140
|
isError: true,
|
|
@@ -153,20 +142,19 @@ export function regReplaceUndo(pi: ExtensionAPI): void {
|
|
|
153
142
|
};
|
|
154
143
|
}
|
|
155
144
|
|
|
156
|
-
|
|
157
|
-
|
|
145
|
+
await writeAtomic(
|
|
146
|
+
mutationTargetPath,
|
|
147
|
+
undo.bom + restoreEndings(undo.content, undo.originalEnding),
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const currentNormalized = currentRaw === undefined ? "" : toLF(stripBOM(currentRaw).text);
|
|
158
151
|
const currentHashes = await lineHashes(currentNormalized, mutationTargetPath);
|
|
159
|
-
const diffResult = genDiff(undo.content,
|
|
152
|
+
const diffResult = genDiff(undo.content, undo.resultContent, 0, undefined, undo.hashes);
|
|
160
153
|
const linesAddedByReplace = cntDiff(diffResult.diff, "+");
|
|
161
154
|
const linesRemovedByReplace = cntDiff(diffResult.diff, "-");
|
|
162
155
|
const restoredRange = changedRange(currentNormalized, undo.content);
|
|
163
156
|
const undoDiff = genDiff(currentNormalized, undo.content, 1, undo.hashes, currentHashes).diff;
|
|
164
157
|
|
|
165
|
-
await writeAtomic(
|
|
166
|
-
mutationTargetPath,
|
|
167
|
-
undo.bom + restoreEndings(undo.content, undo.originalEnding),
|
|
168
|
-
);
|
|
169
|
-
|
|
170
158
|
try {
|
|
171
159
|
const store = await loadHashStore();
|
|
172
160
|
upsertSnapshot(store, mutationTargetPath, contentChecksum(undo.content), splitLines(undo.content).length, undo.hashes);
|
|
@@ -178,8 +166,11 @@ export function regReplaceUndo(pi: ExtensionAPI): void {
|
|
|
178
166
|
await clearUndo(mutationTargetPath);
|
|
179
167
|
|
|
180
168
|
const parts: string[] = [
|
|
181
|
-
`Undone last
|
|
169
|
+
`Undone last change on ${path}.`,
|
|
182
170
|
];
|
|
171
|
+
if (currentRaw === undefined) {
|
|
172
|
+
parts.push("The file was deleted; restored it from undo history.");
|
|
173
|
+
}
|
|
183
174
|
if (linesAddedByReplace > 0 || linesRemovedByReplace > 0) {
|
|
184
175
|
parts.push(
|
|
185
176
|
`Removed ${linesAddedByReplace} line(s), restored ${linesRemovedByReplace} line(s).`,
|
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,28 +24,18 @@ 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";
|
|
46
34
|
import { loadP, loadGuide } from "./prompts";
|
|
47
|
-
import {
|
|
48
|
-
import {
|
|
49
|
-
import { getServed, recordServedSafe, recordServedDiffSafe } from "./served";
|
|
35
|
+
import { loadHashStore, findSnapshotPaths, findServedPaths, type HashStore } from "./hash-store";
|
|
36
|
+
import { getServed, recordServedSafe } from "./served";
|
|
50
37
|
import { noopPayloadKey, markBoundaryNoop, consumeBoundaryBypass, clearBoundaryBypass } from "./boundary-bypass";
|
|
38
|
+
import { commitEdit } from "./commit";
|
|
51
39
|
|
|
52
40
|
const replacementLinesSchema = Type.Array(
|
|
53
41
|
Type.String({
|
|
@@ -93,7 +81,7 @@ export type ReplaceDetails = {
|
|
|
93
81
|
metrics?: RMetrics;
|
|
94
82
|
};
|
|
95
83
|
|
|
96
|
-
interface PipelineResult {
|
|
84
|
+
export interface PipelineResult {
|
|
97
85
|
path: string;
|
|
98
86
|
originalNormalized: string;
|
|
99
87
|
result: string;
|
|
@@ -112,8 +100,6 @@ interface PipelineResult {
|
|
|
112
100
|
boundaryRemovedLines: number;
|
|
113
101
|
}
|
|
114
102
|
|
|
115
|
-
const PREVIEW_DEBOUNCE_MS = 150;
|
|
116
|
-
|
|
117
103
|
const ROOT_KS = new Set(["path", "remove_from", "remove_to", "replacement_lines"]);
|
|
118
104
|
|
|
119
105
|
export function assertReq(
|
|
@@ -162,7 +148,7 @@ async function resolveMissingPath(
|
|
|
162
148
|
} catch {
|
|
163
149
|
return undefined;
|
|
164
150
|
}
|
|
165
|
-
const matches = findSnapshotPaths(store, hashes);
|
|
151
|
+
const matches = [...new Set([...findSnapshotPaths(store, hashes), ...findServedPaths(store, hashes)])];
|
|
166
152
|
if (matches.length === 1) {
|
|
167
153
|
return {
|
|
168
154
|
path: matches[0]!,
|
|
@@ -183,6 +169,7 @@ export interface ExecPipelineOptions {
|
|
|
183
169
|
store?: HashStore;
|
|
184
170
|
noPersist?: boolean;
|
|
185
171
|
skipBoundaryDedup?: boolean;
|
|
172
|
+
preloadedNorm?: NormFile;
|
|
186
173
|
}
|
|
187
174
|
|
|
188
175
|
function collectRemovedHashes(
|
|
@@ -243,7 +230,7 @@ export async function execPipeline(
|
|
|
243
230
|
|
|
244
231
|
const hashStore = options?.store ?? await loadHashStore();
|
|
245
232
|
const { normalized: originalNormalized, bom, originalEnding, fileHashes: originalHashes, hadUtf8DecodeErrors, absolutePath } = await readNormFile(
|
|
246
|
-
path, cwd, { signal: options?.signal, accessMode: options?.accessMode, maxLines: MAX_HASH_LINES, store: hashStore, noPersist: options?.noPersist },
|
|
233
|
+
path, cwd, { signal: options?.signal, accessMode: options?.accessMode, maxLines: MAX_HASH_LINES, store: hashStore, noPersist: options?.noPersist, preloadedNorm: options?.preloadedNorm },
|
|
247
234
|
);
|
|
248
235
|
|
|
249
236
|
const served = await getServed(hashStore, absolutePath);
|
|
@@ -338,21 +325,6 @@ type ToolDef = ToolDefinition<
|
|
|
338
325
|
RRState
|
|
339
326
|
> & { renderShell?: "default" | "self" };
|
|
340
327
|
|
|
341
|
-
export function reuseText(context: any, content: string): Text {
|
|
342
|
-
const t = context.lastComponent instanceof Text
|
|
343
|
-
? context.lastComponent
|
|
344
|
-
: new Text("", 0, 0);
|
|
345
|
-
t.setText(content);
|
|
346
|
-
return t;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
export function reuseMarkdown(context: any, content: string, theme: any): Markdown {
|
|
350
|
-
const m = context.lastComponent instanceof Markdown
|
|
351
|
-
? context.lastComponent
|
|
352
|
-
: new Markdown("", 0, 0, mkMdTheme(theme));
|
|
353
|
-
m.setText(content);
|
|
354
|
-
return m;
|
|
355
|
-
}
|
|
356
328
|
|
|
357
329
|
export function buildToolDef(): ToolDef {
|
|
358
330
|
const E_DESC = loadP("../prompts/replace.md");
|
|
@@ -369,107 +341,17 @@ export function buildToolDef(): ToolDef {
|
|
|
369
341
|
promptGuidelines: E_GUIDE,
|
|
370
342
|
prepareArguments: makePrepareArguments(),
|
|
371
343
|
renderShell: "default",
|
|
372
|
-
renderCall(
|
|
373
|
-
const previewInput = getPreviewInput(args);
|
|
374
|
-
const cancelPendingPreview = () => {
|
|
375
|
-
if (context.state.previewTimer) {
|
|
376
|
-
clearTimeout(context.state.previewTimer);
|
|
377
|
-
context.state.previewTimer = undefined;
|
|
378
|
-
}
|
|
379
|
-
};
|
|
380
|
-
if (context.executionStarted) {
|
|
381
|
-
cancelPendingPreview();
|
|
382
|
-
context.state.argsKey = undefined;
|
|
383
|
-
context.state.preview = undefined;
|
|
384
|
-
context.state.previewGeneration =
|
|
385
|
-
(context.state.previewGeneration ?? 0) + 1;
|
|
386
|
-
} else if (!context.argsComplete || !previewInput) {
|
|
387
|
-
cancelPendingPreview();
|
|
388
|
-
context.state.argsKey = undefined;
|
|
389
|
-
context.state.preview = undefined;
|
|
390
|
-
context.state.previewGeneration =
|
|
391
|
-
(context.state.previewGeneration ?? 0) + 1;
|
|
392
|
-
} else {
|
|
393
|
-
const argsKey = JSON.stringify(previewInput);
|
|
394
|
-
if (context.state.argsKey !== argsKey) {
|
|
395
|
-
cancelPendingPreview();
|
|
396
|
-
context.state.argsKey = argsKey;
|
|
397
|
-
context.state.preview = undefined;
|
|
398
|
-
const previewGeneration = (context.state.previewGeneration ?? 0) + 1;
|
|
399
|
-
context.state.previewGeneration = previewGeneration;
|
|
400
|
-
context.state.previewTimer = setTimeout(() => {
|
|
401
|
-
context.state.previewTimer = undefined;
|
|
402
|
-
compPreview(args, context.cwd)
|
|
403
|
-
.then((preview) => {
|
|
404
|
-
if (
|
|
405
|
-
context.state.argsKey === argsKey &&
|
|
406
|
-
context.state.previewGeneration === previewGeneration
|
|
407
|
-
) {
|
|
408
|
-
context.state.preview = preview;
|
|
409
|
-
context.invalidate();
|
|
410
|
-
}
|
|
411
|
-
})
|
|
412
|
-
.catch((err: unknown) => {
|
|
413
|
-
if (
|
|
414
|
-
context.state.argsKey === argsKey &&
|
|
415
|
-
context.state.previewGeneration === previewGeneration
|
|
416
|
-
) {
|
|
417
|
-
context.state.preview = {
|
|
418
|
-
error: err instanceof Error ? err.message : String(err),
|
|
419
|
-
};
|
|
420
|
-
context.invalidate();
|
|
421
|
-
}
|
|
422
|
-
});
|
|
423
|
-
}, PREVIEW_DEBOUNCE_MS);
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
const text =
|
|
427
|
-
(context.lastComponent as Text | undefined) ?? new Text("", 0, 0);
|
|
428
|
-
text.setText(
|
|
429
|
-
fmtCall(
|
|
430
|
-
getPreviewInput(args) ?? undefined,
|
|
431
|
-
context.state as RRState,
|
|
432
|
-
context.expanded,
|
|
433
|
-
theme,
|
|
434
|
-
),
|
|
435
|
-
);
|
|
436
|
-
return text;
|
|
437
|
-
},
|
|
438
|
-
|
|
344
|
+
renderCall: makeRenderCall(compPreview),
|
|
439
345
|
renderResult(result, { isPartial }, theme, context) {
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
const renderState = context.state as RRState | undefined;
|
|
451
|
-
if (renderState) {
|
|
452
|
-
if (renderState.previewTimer) {
|
|
453
|
-
clearTimeout(renderState.previewTimer);
|
|
454
|
-
renderState.previewTimer = undefined;
|
|
455
|
-
}
|
|
456
|
-
renderState.preview = undefined;
|
|
457
|
-
renderState.previewGeneration = (renderState.previewGeneration ?? 0) + 1;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
if (context.isError) {
|
|
461
|
-
return renderedText
|
|
462
|
-
? reuseText(context, `\n${theme.fg("error", renderedText)}`)
|
|
463
|
-
: new Text("", 0, 0);
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
if (isApplied(typedResult.details)) {
|
|
467
|
-
const appliedText = buildAppliedText(renderedText, typedResult.details, theme);
|
|
468
|
-
return appliedText ? reuseText(context, appliedText) : new Text("", 0, 0);
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
if (!renderedText) return new Text("", 0, 0);
|
|
472
|
-
return reuseMarkdown(context, fmtResultMd(renderedText), theme);
|
|
346
|
+
return renderEditResult(
|
|
347
|
+
result as {
|
|
348
|
+
content?: Array<{ type: string; text?: string }>;
|
|
349
|
+
details?: ReplaceDetails;
|
|
350
|
+
},
|
|
351
|
+
isPartial,
|
|
352
|
+
theme,
|
|
353
|
+
context,
|
|
354
|
+
);
|
|
473
355
|
},
|
|
474
356
|
|
|
475
357
|
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
@@ -488,113 +370,24 @@ export function buildToolDef(): ToolDef {
|
|
|
488
370
|
const boundaryBypass = consumeBoundaryBypass(mutationTargetPath, noopPayload);
|
|
489
371
|
return withFileMutationQueue(mutationTargetPath, async () => {
|
|
490
372
|
abortIf(signal);
|
|
491
|
-
|
|
492
|
-
const {
|
|
493
|
-
originalNormalized,
|
|
494
|
-
originalHashes,
|
|
495
|
-
result,
|
|
496
|
-
bom,
|
|
497
|
-
originalEnding,
|
|
498
|
-
hadUtf8DecodeErrors,
|
|
499
|
-
warnings,
|
|
500
|
-
noopEdit,
|
|
501
|
-
firstChangedLine,
|
|
502
|
-
lastChangedLine,
|
|
503
|
-
resultHashes,
|
|
504
|
-
hadBoundaryDedup,
|
|
505
|
-
boundaryRemovedLines,
|
|
506
|
-
totalAddedLines,
|
|
507
|
-
totalRemovedLines,
|
|
508
|
-
} = await execPipeline(
|
|
373
|
+
const pipe = await execPipeline(
|
|
509
374
|
normalizedParams,
|
|
510
375
|
ctx.cwd,
|
|
511
376
|
{ accessMode: constants.R_OK | constants.W_OK, signal, skipBoundaryDedup: boundaryBypass },
|
|
512
377
|
);
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
if (boundaryBypass && originalNormalized !== result) {
|
|
518
|
-
warnings.push("[E_BOUNDARY_BYPASS] Boundary dedup was off for this call and is back on.");
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
const editsAttempted = 1;
|
|
522
|
-
if (originalNormalized === result) {
|
|
523
|
-
const noopSnapshotId = await safeSnapId(absolutePath, "noop edit");
|
|
524
|
-
if (hadBoundaryDedup) {
|
|
525
|
-
markBoundaryNoop(mutationTargetPath, noopPayload);
|
|
526
|
-
}
|
|
527
|
-
return buildNoop({
|
|
528
|
-
path,
|
|
529
|
-
noopEdit,
|
|
530
|
-
snapshotId: noopSnapshotId,
|
|
531
|
-
editMeta: {
|
|
532
|
-
editsAttempted,
|
|
533
|
-
noopEditsCount: noopEdit ? 1 : 0,
|
|
534
|
-
addedLines: 0,
|
|
535
|
-
removedLines: 0,
|
|
536
|
-
},
|
|
537
|
-
warnings,
|
|
538
|
-
boundaryRemovedLines,
|
|
539
|
-
});
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
if (hadUtf8DecodeErrors) {
|
|
543
|
-
warnings.push(
|
|
544
|
-
"Non-UTF-8 bytes were shown as U+FFFD; this edit rewrote the file as UTF-8.",
|
|
545
|
-
);
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
abortIf(signal);
|
|
549
|
-
const undo = await saveUndo(mutationTargetPath, {
|
|
550
|
-
content: originalNormalized,
|
|
551
|
-
bom,
|
|
552
|
-
originalEnding,
|
|
553
|
-
hashes: originalHashes,
|
|
554
|
-
resultContent: result,
|
|
555
|
-
});
|
|
556
|
-
if (!undo.persisted) {
|
|
557
|
-
throw new Error(
|
|
558
|
-
`[E_UNDO_UNAVAILABLE] Could not persist undo history; the edit was not applied and ${path} is unchanged.`
|
|
559
|
-
);
|
|
560
|
-
}
|
|
561
|
-
try {
|
|
562
|
-
abortIf(signal);
|
|
563
|
-
await writeAtomic(
|
|
564
|
-
absolutePath,
|
|
565
|
-
bom + restoreEndings(result, originalEnding),
|
|
566
|
-
);
|
|
567
|
-
} catch (error) {
|
|
568
|
-
await undo.restore();
|
|
569
|
-
throw error;
|
|
570
|
-
}
|
|
571
|
-
clearBoundaryBypass(mutationTargetPath);
|
|
572
|
-
const updatedSnapshotId = await safeSnapId(absolutePath, "post-edit");
|
|
573
|
-
|
|
574
|
-
const editMeta: RMeta = {
|
|
575
|
-
editsAttempted,
|
|
576
|
-
noopEditsCount: noopEdit ? 1 : 0,
|
|
577
|
-
firstChangedLine,
|
|
578
|
-
lastChangedLine,
|
|
579
|
-
addedLines: totalAddedLines,
|
|
580
|
-
removedLines: totalRemovedLines,
|
|
581
|
-
};
|
|
582
|
-
|
|
583
|
-
const successInput = {
|
|
378
|
+
const appliedWarnings = boundaryBypass
|
|
379
|
+
? ["[E_BOUNDARY_BYPASS] Boundary dedup was off for this call and is back on."]
|
|
380
|
+
: [];
|
|
381
|
+
return commitEdit(pipe, {
|
|
584
382
|
path,
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
};
|
|
593
|
-
const changed = buildChanged(successInput);
|
|
594
|
-
if (changed.details.diff) {
|
|
595
|
-
await recordServedDiffSafe(mutationTargetPath, changed.details.diff, "post-edit diff", new Set(resultHashes));
|
|
596
|
-
}
|
|
597
|
-
return changed;
|
|
383
|
+
absolutePath,
|
|
384
|
+
mutationTargetPath,
|
|
385
|
+
signal,
|
|
386
|
+
prefixWarnings: resolution ? [resolution.warning] : [],
|
|
387
|
+
appliedWarnings,
|
|
388
|
+
onApplied: () => clearBoundaryBypass(mutationTargetPath),
|
|
389
|
+
onNoopDedup: () => markBoundaryNoop(mutationTargetPath, noopPayload),
|
|
390
|
+
});
|
|
598
391
|
});
|
|
599
392
|
},
|
|
600
393
|
};
|
package/src/served.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { loadHashStore,
|
|
1
|
+
import { loadHashStore, parseStoredHashes, type HashStore } from "./hash-store";
|
|
2
2
|
import { HASH_CLASS } from "./hashline/alphabet";
|
|
3
3
|
|
|
4
4
|
const SERVED_DIFF_ROW_RE = new RegExp(`^[+ ](${HASH_CLASS})│`);
|
|
@@ -14,8 +14,7 @@ export function servedHashesFromDiff(diff: string): string[] {
|
|
|
14
14
|
|
|
15
15
|
export function getServed(store: HashStore, path: string): Set<string> | undefined {
|
|
16
16
|
const row = store.stmts.servedGet(path);
|
|
17
|
-
|
|
18
|
-
const parsed = parseHashList(row.hashes as string, () => store.stmts.servedDelete(path));
|
|
17
|
+
const parsed = parseStoredHashes(row, () => store.stmts.servedDelete(path));
|
|
19
18
|
if (!parsed) return undefined;
|
|
20
19
|
return new Set(parsed);
|
|
21
20
|
}
|
|
@@ -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).
|