pi-hashline-edit-pro 4.1.0 → 4.2.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 +12 -8
- package/index.ts +27 -5
- package/package.json +1 -1
- package/prompts/insert-guidelines.md +1 -0
- package/prompts/insert.md +1 -1
- package/prompts/replace-guidelines.md +2 -1
- package/prompts/replace.md +1 -1
- package/src/anchor-registry.ts +6 -6
- package/src/batch.ts +615 -0
- package/src/commit.ts +3 -2
- package/src/config-ui.ts +58 -11
- package/src/config.ts +49 -8
- package/src/edit-common.ts +14 -12
- package/src/grep.ts +1 -1
- package/src/hashline/apply.ts +128 -84
- package/src/hashline/index.ts +3 -0
- package/src/insert.ts +61 -51
- package/src/payload-contract.ts +30 -2
- package/src/replace-response.ts +2 -2
- package/src/replace-undo.ts +3 -2
- package/src/replace.ts +65 -25
- package/src/write-hook.ts +2 -2
package/src/insert.ts
CHANGED
|
@@ -3,43 +3,17 @@ import { Type } from "typebox";
|
|
|
3
3
|
import { constants } from "fs";
|
|
4
4
|
import { execPipeline, type ReqParams, type ReplaceDetails, previewFromPipe, previewError } from "./replace";
|
|
5
5
|
import { commitEdit } from "./commit";
|
|
6
|
+
import { batchMemberFor, executeBatchMember, noteBatchFailure } from "./batch";
|
|
6
7
|
import { readNormFile, type NormFile } from "./file-reader";
|
|
7
|
-
import { MAX_HASH_LINES, parseHashRef, resolveAnchorLine, type Anchor } from "./hashline";
|
|
8
|
+
import { MAX_HASH_LINES, parseHashRef, resEdit, resolveAnchorLine, type Anchor, type HEdit } from "./hashline";
|
|
8
9
|
import { stripAnchorRow } from "./hashline/resolve";
|
|
9
10
|
import { loadP, loadGuide } from "./prompts";
|
|
10
|
-
import { normReq } from "./payload-contract";
|
|
11
|
-
import { decodeStringArray, isRec,
|
|
11
|
+
import { assertInsertReq, normReq, type InsertReq } from "./payload-contract";
|
|
12
|
+
import { decodeStringArray, isRec, splitLines } from "./utils";
|
|
12
13
|
import { clearBoundaryBypass } from "./boundary-bypass";
|
|
13
14
|
import { queuedEdit, editToolBase, editRenderCallWrapper, editRenderResultWrapper, resolveEditTargetWithRequirement, throwIfStrictInput, withInsertPrompts, DEFAULT_EDIT_FLAGS, type EditToolFlags } from "./edit-common";
|
|
14
15
|
import type { RPreview, RRState } from "./replace-render";
|
|
15
|
-
|
|
16
|
-
const INSERT_KS = new Set(["path", "anchor", "direction", "lines"]);
|
|
17
|
-
|
|
18
|
-
export interface InsertReq {
|
|
19
|
-
path?: string;
|
|
20
|
-
anchor: string;
|
|
21
|
-
direction: "before" | "after";
|
|
22
|
-
lines: string[];
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function assertInsertReq(request: unknown): asserts request is InsertReq {
|
|
26
|
-
if (!isRec(request)) {
|
|
27
|
-
throw new Error("[E_BAD_SHAPE] Insert request must be an object.");
|
|
28
|
-
}
|
|
29
|
-
rejectUnknownFields(request, INSERT_KS, "Insert request");
|
|
30
|
-
if (request.path !== undefined && typeof request.path !== "string") {
|
|
31
|
-
throw new Error('[E_BAD_SHAPE] Insert request field "path" must be a string when provided.');
|
|
32
|
-
}
|
|
33
|
-
if (typeof request.anchor !== "string" || request.anchor.length === 0) {
|
|
34
|
-
throw new Error('[E_BAD_SHAPE] Insert request requires an "anchor" string (4-char anchor from read output).');
|
|
35
|
-
}
|
|
36
|
-
if (request.direction !== "before" && request.direction !== "after") {
|
|
37
|
-
throw new Error('[E_BAD_SHAPE] Insert request "direction" must be "before" or "after".');
|
|
38
|
-
}
|
|
39
|
-
if (!Array.isArray(request.lines) || request.lines.some((line) => typeof line !== "string")) {
|
|
40
|
-
throw new Error('[E_BAD_SHAPE] Insert request requires "lines" as an array of strings, one element per line.');
|
|
41
|
-
}
|
|
42
|
-
}
|
|
16
|
+
export { assertInsertReq, type InsertReq };
|
|
43
17
|
|
|
44
18
|
const insertAnchorSchema = Type.String({
|
|
45
19
|
description:
|
|
@@ -71,7 +45,7 @@ const insertToolSchema = Type.Object(
|
|
|
71
45
|
direction: insertDirectionSchema,
|
|
72
46
|
lines: insertLinesSchema,
|
|
73
47
|
},
|
|
74
|
-
{ additionalProperties:
|
|
48
|
+
{ additionalProperties: true },
|
|
75
49
|
);
|
|
76
50
|
|
|
77
51
|
export function buildInsertToolSchema(requirePath: boolean): typeof insertToolSchema {
|
|
@@ -83,18 +57,18 @@ export function buildInsertToolSchema(requirePath: boolean): typeof insertToolSc
|
|
|
83
57
|
direction: insertDirectionSchema,
|
|
84
58
|
lines: insertLinesSchema,
|
|
85
59
|
},
|
|
86
|
-
{ additionalProperties:
|
|
60
|
+
{ additionalProperties: true },
|
|
87
61
|
) as typeof insertToolSchema;
|
|
88
62
|
}
|
|
89
63
|
|
|
90
|
-
function parseInsertAnchor(raw: string): { ref: Anchor; warnings: string[] } {
|
|
64
|
+
export function parseInsertAnchor(raw: string): { ref: Anchor; warnings: string[] } {
|
|
91
65
|
const trimmedAnchor = raw.trim();
|
|
92
66
|
const warnings: string[] = [];
|
|
93
67
|
const anchorText = stripAnchorRow(trimmedAnchor, "anchor entry", warnings);
|
|
94
68
|
return { ref: parseHashRef(anchorText), warnings };
|
|
95
69
|
}
|
|
96
70
|
|
|
97
|
-
function buildInsertEdit(
|
|
71
|
+
export function buildInsertEdit(
|
|
98
72
|
req: InsertReq,
|
|
99
73
|
preload: NormFile,
|
|
100
74
|
ref: Anchor,
|
|
@@ -216,32 +190,68 @@ export function buildInsertToolDef(flags: EditToolFlags = DEFAULT_EDIT_FLAGS): I
|
|
|
216
190
|
anchor: req.anchor,
|
|
217
191
|
providedPath: req.path,
|
|
218
192
|
cwd: ctx.cwd,
|
|
193
|
+
}).catch((error: unknown) => {
|
|
194
|
+
const member = batchMemberFor(_toolCallId);
|
|
195
|
+
if (member) noteBatchFailure(member, error);
|
|
196
|
+
throw error;
|
|
219
197
|
});
|
|
220
|
-
|
|
221
|
-
|
|
198
|
+
let ref: Anchor;
|
|
199
|
+
let anchorWarnings: string[];
|
|
200
|
+
try {
|
|
201
|
+
({ ref, warnings: anchorWarnings } = parseInsertAnchor(req.anchor));
|
|
202
|
+
await throwIfStrictInput([...anchorWarnings, ...insertWarnings]);
|
|
203
|
+
} catch (error) {
|
|
204
|
+
const member = batchMemberFor(_toolCallId);
|
|
205
|
+
if (member) noteBatchFailure(member, error);
|
|
206
|
+
throw error;
|
|
207
|
+
}
|
|
222
208
|
return queuedEdit(targetPath, ctx.cwd, signal, async (absolutePath, mutationTargetPath) => {
|
|
209
|
+
const member = batchMemberFor(_toolCallId);
|
|
223
210
|
const preload = await readNormFile(targetPath, ctx.cwd, {
|
|
224
211
|
signal,
|
|
225
212
|
accessMode: constants.R_OK | constants.W_OK,
|
|
226
213
|
maxLines: MAX_HASH_LINES,
|
|
227
214
|
});
|
|
228
215
|
const { editParams, anchorLine } = buildInsertEdit(req, preload, ref, targetPath);
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
216
|
+
if (!member) {
|
|
217
|
+
const pipe = await execPipeline(targetPath, editParams, ctx.cwd, {
|
|
218
|
+
accessMode: constants.R_OK | constants.W_OK,
|
|
219
|
+
signal,
|
|
220
|
+
preloadedNorm: preload,
|
|
221
|
+
skipBoundaryDedup: true,
|
|
222
|
+
});
|
|
223
|
+
return commitEdit(pipe, {
|
|
224
|
+
path: pipe.path,
|
|
225
|
+
absolutePath,
|
|
226
|
+
mutationTargetPath,
|
|
227
|
+
signal,
|
|
228
|
+
verb: "inserted",
|
|
229
|
+
noopNoun: "Insertion",
|
|
230
|
+
foldedAnchorLines: anchorLine === undefined ? 0 : 1,
|
|
231
|
+
prefixWarnings: [...anchorWarnings, ...insertWarnings],
|
|
232
|
+
onApplied: () => clearBoundaryBypass(mutationTargetPath),
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
let hedit: HEdit;
|
|
236
|
+
const resWarnings: string[] = [];
|
|
237
|
+
try {
|
|
238
|
+
hedit = resEdit(editParams, resWarnings);
|
|
239
|
+
} catch (error) {
|
|
240
|
+
noteBatchFailure(member, error);
|
|
241
|
+
throw error;
|
|
242
|
+
}
|
|
243
|
+
return executeBatchMember({
|
|
244
|
+
kind: "insert",
|
|
245
|
+
member,
|
|
246
|
+
targetPath,
|
|
238
247
|
mutationTargetPath,
|
|
248
|
+
cwd: ctx.cwd,
|
|
239
249
|
signal,
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
250
|
+
hedit,
|
|
251
|
+
extraWarnings: [...anchorWarnings, ...insertWarnings, ...resWarnings],
|
|
252
|
+
skipBoundaryDedup: true,
|
|
253
|
+
strictBoundaryDedup: false,
|
|
254
|
+
foldedLines: anchorLine === undefined ? 0 : 1,
|
|
245
255
|
});
|
|
246
256
|
});
|
|
247
257
|
},
|
package/src/payload-contract.ts
CHANGED
|
@@ -32,7 +32,7 @@ export const editToolSchema = Type.Object(
|
|
|
32
32
|
remove_to: removeToSchema,
|
|
33
33
|
replacement_lines: replacementLinesSchema,
|
|
34
34
|
},
|
|
35
|
-
{ additionalProperties:
|
|
35
|
+
{ additionalProperties: true },
|
|
36
36
|
);
|
|
37
37
|
|
|
38
38
|
export function buildEditToolSchema(requirePath: boolean): typeof editToolSchema {
|
|
@@ -44,7 +44,7 @@ export function buildEditToolSchema(requirePath: boolean): typeof editToolSchema
|
|
|
44
44
|
remove_to: removeToSchema,
|
|
45
45
|
replacement_lines: replacementLinesSchema,
|
|
46
46
|
},
|
|
47
|
-
{ additionalProperties:
|
|
47
|
+
{ additionalProperties: true },
|
|
48
48
|
) as typeof editToolSchema;
|
|
49
49
|
}
|
|
50
50
|
|
|
@@ -109,3 +109,31 @@ export function getPreviewInput(args: unknown): { path?: string; remove_from: st
|
|
|
109
109
|
replacement_lines: normalized.replacement_lines as string[],
|
|
110
110
|
};
|
|
111
111
|
}
|
|
112
|
+
|
|
113
|
+
const INSERT_KS = new Set(["path", "anchor", "direction", "lines"]);
|
|
114
|
+
|
|
115
|
+
export interface InsertReq {
|
|
116
|
+
path?: string;
|
|
117
|
+
anchor: string;
|
|
118
|
+
direction: "before" | "after";
|
|
119
|
+
lines: string[];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function assertInsertReq(request: unknown): asserts request is InsertReq {
|
|
123
|
+
if (!isRec(request)) {
|
|
124
|
+
throw new Error("[E_BAD_SHAPE] Insert request must be an object.");
|
|
125
|
+
}
|
|
126
|
+
rejectUnknownFields(request, INSERT_KS, "Insert request");
|
|
127
|
+
if (request.path !== undefined && typeof request.path !== "string") {
|
|
128
|
+
throw new Error('[E_BAD_SHAPE] Insert request field "path" must be a string when provided.');
|
|
129
|
+
}
|
|
130
|
+
if (typeof request.anchor !== "string" || request.anchor.length === 0) {
|
|
131
|
+
throw new Error('[E_BAD_SHAPE] Insert request requires an "anchor" string (4-char anchor from read output).');
|
|
132
|
+
}
|
|
133
|
+
if (request.direction !== "before" && request.direction !== "after") {
|
|
134
|
+
throw new Error('[E_BAD_SHAPE] Insert request "direction" must be "before" or "after".');
|
|
135
|
+
}
|
|
136
|
+
if (!Array.isArray(request.lines) || request.lines.some((line) => typeof line !== "string")) {
|
|
137
|
+
throw new Error('[E_BAD_SHAPE] Insert request requires "lines" as an array of strings, one element per line.');
|
|
138
|
+
}
|
|
139
|
+
}
|
package/src/replace-response.ts
CHANGED
|
@@ -170,10 +170,10 @@ export function withDedupRows(diff: string, lineNumbers: (number | undefined)[],
|
|
|
170
170
|
return { diff: out.join("\n"), lineNumbers: nums };
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
-
export function buildChanged(input: SuccessInput, verb = "replaced"): TResult {
|
|
173
|
+
export function buildChanged(input: SuccessInput, verb = "replaced", diffContextLines = 1): TResult {
|
|
174
174
|
const { path, result, warnings, snapshotId, originalNormalized, originalHashes, editMeta, resultHashes, boundaryDedupAbove, boundaryDedupBelow } = input;
|
|
175
175
|
const resultLines = visLines(result);
|
|
176
|
-
const baseDiff = genDiff(originalNormalized, result,
|
|
176
|
+
const baseDiff = genDiff(originalNormalized, result, diffContextLines, resultHashes, originalHashes);
|
|
177
177
|
const diffResult = withDedupRows(baseDiff.diff, baseDiff.lineNumbers, boundaryDedupAbove, boundaryDedupBelow);
|
|
178
178
|
const addedLines = editMeta.addedLines;
|
|
179
179
|
const removedLines = editMeta.removedLines;
|
package/src/replace-undo.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { markServed as markServedScoped, freeAnchors, adoptAnchors } from "./anc
|
|
|
11
11
|
import { resolveInCwd, writeAtomic, type FileIdentity } from "./fs-write";
|
|
12
12
|
import { toLF, stripBOM, restoreEndings, type LineEnding } from "./normalize";
|
|
13
13
|
import { genDiff, genPatch } from "./replace-diff";
|
|
14
|
+
import { getDiffContextLines } from "./config";
|
|
14
15
|
import { cntDiff, errCode, makePrepareArguments, splitLines } from "./utils";
|
|
15
16
|
import { loadP, loadGuide } from "./prompts";
|
|
16
17
|
import { buildMetrics } from "./replace-response";
|
|
@@ -178,13 +179,13 @@ export function regUndo(pi: ExtensionAPI): void {
|
|
|
178
179
|
const linesAddedByReplace = cntDiff(diffResult.diff, "+");
|
|
179
180
|
const linesRemovedByReplace = cntDiff(diffResult.diff, "-");
|
|
180
181
|
const restoredRange = changedRange(currentNormalized, undo.content);
|
|
181
|
-
const undoDiffResult = genDiff(currentNormalized, undo.content,
|
|
182
|
+
const undoDiffResult = genDiff(currentNormalized, undo.content, await getDiffContextLines(), undo.hashes, currentHashes);
|
|
182
183
|
const undoDiff = undoDiffResult.diff;
|
|
183
184
|
|
|
184
185
|
try {
|
|
185
186
|
const store = await loadHashStore();
|
|
186
187
|
const undoLines = splitLines(undo.content);
|
|
187
|
-
persistSnapshot(store, mutationTargetPath, undo.content, undo.hashes);
|
|
188
|
+
persistSnapshot(store, mutationTargetPath, undo.content, undo.hashes, undoLines.map((line) => contentChecksum(hashSource(line))));
|
|
188
189
|
freeAnchors(mutationTargetPath);
|
|
189
190
|
adoptAnchors(
|
|
190
191
|
mutationTargetPath,
|
package/src/replace.ts
CHANGED
|
@@ -22,7 +22,6 @@ import { applyEdit,
|
|
|
22
22
|
type HEdit,
|
|
23
23
|
type NEdit,
|
|
24
24
|
} from "./hashline";
|
|
25
|
-
import { commitEdit } from "./commit";
|
|
26
25
|
import { withDedupRows, type RMetrics } from "./replace-response";
|
|
27
26
|
import {
|
|
28
27
|
type RPreview,
|
|
@@ -33,7 +32,9 @@ import { adoptAnchors, servedForPath } from "./anchor-registry";
|
|
|
33
32
|
import { resolveTarget } from "./fs-write";
|
|
34
33
|
import { toCwd } from "./paths";
|
|
35
34
|
import { noopPayloadKey, markBoundaryNoop, consumeBoundaryBypass, clearBoundaryBypass } from "./boundary-bypass";
|
|
36
|
-
import { queuedEdit, editToolBase, editRenderCallWrapper, editRenderResultWrapper, resolveEditTargetWithRequirement, throwIfStrictInput,
|
|
35
|
+
import { queuedEdit, editToolBase, editRenderCallWrapper, editRenderResultWrapper, resolveEditTargetWithRequirement, throwIfStrictInput, getBoundaryDedupMode, withReplacePrompts, DEFAULT_EDIT_FLAGS, type EditToolFlags } from "./edit-common";
|
|
36
|
+
import { commitEdit } from "./commit";
|
|
37
|
+
import { batchMemberFor, executeBatchMember, noteBatchFailure } from "./batch";
|
|
37
38
|
|
|
38
39
|
export { editToolSchema, type ReqParams, assertReq };
|
|
39
40
|
|
|
@@ -47,6 +48,7 @@ export type ReplaceDetails = {
|
|
|
47
48
|
metrics?: RMetrics;
|
|
48
49
|
diffLineNumbers?: (number|undefined)[];
|
|
49
50
|
warnings?: string[];
|
|
51
|
+
batch?: { id: number; size: number; last: boolean; total: number };
|
|
50
52
|
};
|
|
51
53
|
|
|
52
54
|
export interface PipelineResult {
|
|
@@ -112,13 +114,7 @@ function countLineChanges(
|
|
|
112
114
|
};
|
|
113
115
|
}
|
|
114
116
|
|
|
115
|
-
export
|
|
116
|
-
targetPath: string,
|
|
117
|
-
params: ReqParams,
|
|
118
|
-
cwd: string,
|
|
119
|
-
options?: ExecPipelineOptions,
|
|
120
|
-
): Promise<PipelineResult> {
|
|
121
|
-
|
|
117
|
+
export function buildReplaceHEdit(params: ReqParams): { edit: HEdit; warnings: string[] } {
|
|
122
118
|
const editWarnings: string[] = [];
|
|
123
119
|
let replacementLines = params.replacement_lines;
|
|
124
120
|
const expandedReplacement = decodeStringArray(replacementLines);
|
|
@@ -134,7 +130,17 @@ export async function execPipeline(
|
|
|
134
130
|
},
|
|
135
131
|
editWarnings,
|
|
136
132
|
);
|
|
133
|
+
return { edit, warnings: editWarnings };
|
|
134
|
+
}
|
|
137
135
|
|
|
136
|
+
export async function execPipeline(
|
|
137
|
+
targetPath: string,
|
|
138
|
+
params: ReqParams,
|
|
139
|
+
cwd: string,
|
|
140
|
+
options?: ExecPipelineOptions,
|
|
141
|
+
): Promise<PipelineResult> {
|
|
142
|
+
|
|
143
|
+
const { edit, warnings: editWarnings } = buildReplaceHEdit(params);
|
|
138
144
|
const hashStore = options?.store ?? await loadHashStore();
|
|
139
145
|
const preResolvedPath = await resolveTarget(toCwd(targetPath, cwd));
|
|
140
146
|
const served = servedForPath(preResolvedPath);
|
|
@@ -143,8 +149,9 @@ export async function execPipeline(
|
|
|
143
149
|
);
|
|
144
150
|
const displayPath = relative(cwd, absolutePath).replace(/\\/g, "/") || targetPath;
|
|
145
151
|
|
|
146
|
-
const
|
|
147
|
-
const effectiveSkipBoundaryDedup = options?.skipBoundaryDedup === true ||
|
|
152
|
+
const dedupMode = await getBoundaryDedupMode();
|
|
153
|
+
const effectiveSkipBoundaryDedup = options?.skipBoundaryDedup === true || dedupMode === "off";
|
|
154
|
+
const strictBoundaryDedup = options?.skipBoundaryDedup !== true && dedupMode === "strict";
|
|
148
155
|
let anchorResult: ReturnType<typeof applyEdit>;
|
|
149
156
|
try {
|
|
150
157
|
anchorResult = applyEdit(
|
|
@@ -155,6 +162,7 @@ export async function execPipeline(
|
|
|
155
162
|
displayPath,
|
|
156
163
|
served,
|
|
157
164
|
effectiveSkipBoundaryDedup,
|
|
165
|
+
strictBoundaryDedup,
|
|
158
166
|
);
|
|
159
167
|
} catch (error) {
|
|
160
168
|
await noteAnchorError(absolutePath, error, originalHashes, options?.noPersist);
|
|
@@ -275,29 +283,61 @@ export function buildToolDef(flags: EditToolFlags = DEFAULT_EDIT_FLAGS): ToolDef
|
|
|
275
283
|
removeTo: normalizedParams.remove_to,
|
|
276
284
|
providedPath: normalizedParams.path,
|
|
277
285
|
cwd: ctx.cwd,
|
|
286
|
+
}).catch((error: unknown) => {
|
|
287
|
+
const member = batchMemberFor(_toolCallId);
|
|
288
|
+
if (member) noteBatchFailure(member, error);
|
|
289
|
+
throw error;
|
|
278
290
|
});
|
|
279
291
|
return queuedEdit(targetPath, ctx.cwd, signal, async (absolutePath, mutationTargetPath) => {
|
|
280
|
-
const
|
|
292
|
+
const dedupMode = await getBoundaryDedupMode();
|
|
293
|
+
const dedupOn = dedupMode !== "off";
|
|
281
294
|
const noopPayload = noopPayloadKey(mutationTargetPath, normalizedParams.remove_from, normalizedParams.remove_to, normalizedParams.replacement_lines);
|
|
282
295
|
const boundaryBypass = dedupOn ? consumeBoundaryBypass(mutationTargetPath, noopPayload) : false;
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
296
|
+
const strictBoundaryDedup = dedupMode === "strict" && !boundaryBypass;
|
|
297
|
+
const member = batchMemberFor(_toolCallId);
|
|
298
|
+
if (!member) {
|
|
299
|
+
const pipe = await execPipeline(
|
|
300
|
+
targetPath,
|
|
301
|
+
normalizedParams,
|
|
302
|
+
ctx.cwd,
|
|
303
|
+
{ accessMode: constants.R_OK | constants.W_OK, signal, skipBoundaryDedup: boundaryBypass },
|
|
304
|
+
);
|
|
305
|
+
const appliedWarnings = boundaryBypass
|
|
306
|
+
? ["[W_BOUNDARY_BYPASS] Boundary dedup was off for this call and is back on."]
|
|
307
|
+
: [];
|
|
308
|
+
return commitEdit(pipe, {
|
|
309
|
+
path: pipe.path,
|
|
310
|
+
absolutePath,
|
|
311
|
+
mutationTargetPath,
|
|
312
|
+
editAnchors: [normalizedParams.remove_from, normalizedParams.remove_to],
|
|
313
|
+
signal,
|
|
314
|
+
appliedWarnings,
|
|
315
|
+
onApplied: () => { if (dedupOn) clearBoundaryBypass(mutationTargetPath); },
|
|
316
|
+
onNoopDedup: dedupOn ? () => markBoundaryNoop(mutationTargetPath, noopPayload) : undefined,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
let built: { edit: HEdit; warnings: string[] };
|
|
320
|
+
try {
|
|
321
|
+
built = buildReplaceHEdit(normalizedParams);
|
|
322
|
+
} catch (error) {
|
|
323
|
+
noteBatchFailure(member, error);
|
|
324
|
+
throw error;
|
|
325
|
+
}
|
|
289
326
|
const appliedWarnings = boundaryBypass
|
|
290
327
|
? ["[W_BOUNDARY_BYPASS] Boundary dedup was off for this call and is back on."]
|
|
291
328
|
: [];
|
|
292
|
-
return
|
|
293
|
-
|
|
294
|
-
|
|
329
|
+
return executeBatchMember({
|
|
330
|
+
kind: "replace",
|
|
331
|
+
member,
|
|
332
|
+
targetPath,
|
|
295
333
|
mutationTargetPath,
|
|
296
|
-
|
|
334
|
+
cwd: ctx.cwd,
|
|
297
335
|
signal,
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
336
|
+
hedit: built.edit,
|
|
337
|
+
extraWarnings: [...built.warnings, ...appliedWarnings],
|
|
338
|
+
skipBoundaryDedup: boundaryBypass,
|
|
339
|
+
strictBoundaryDedup,
|
|
340
|
+
noopPayload,
|
|
301
341
|
});
|
|
302
342
|
});
|
|
303
343
|
},
|
package/src/write-hook.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { HASH_CLASS } from "./hashline/alphabet";
|
|
3
3
|
import { HASH_SEP } from "./hashline/hash";
|
|
4
|
-
import {
|
|
4
|
+
import { servedForPath } from "./anchor-registry";
|
|
5
5
|
import { resolveInCwd } from "./fs-write";
|
|
6
6
|
import { abortIf, splitLines, isRec, normalizeFilePath } from "./utils";
|
|
7
7
|
|
|
@@ -27,7 +27,7 @@ export async function servedHashEchoDenial(rawPath: string, content: string, cwd
|
|
|
27
27
|
abortIf(signal);
|
|
28
28
|
const { resolved } = await resolveInCwd(rawPath, cwd);
|
|
29
29
|
abortIf(signal);
|
|
30
|
-
const served =
|
|
30
|
+
const served = servedForPath(resolved);
|
|
31
31
|
if (!served || served.size === 0) return undefined;
|
|
32
32
|
const echo = findServedHashEcho(content, served);
|
|
33
33
|
if (!echo) return undefined;
|