pi-hashline-edit-pro 4.2.1 → 4.2.2
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 +4 -4
- package/index.ts +3 -2
- package/package.json +1 -1
- package/src/anchor-registry.ts +64 -5
- package/src/batch.ts +126 -29
- package/src/commit.ts +29 -7
- package/src/config.ts +88 -37
- package/src/grep.ts +12 -4
- package/src/hashline/alphabet.ts +5 -5
- package/src/hashline/anchor-table.json +1 -1
- package/src/hashline/apply.ts +3 -1
- package/src/hashline/resolve.ts +1 -1
- package/src/insert.ts +42 -38
- package/src/replace.ts +29 -20
- package/src/validation.ts +2 -2
- package/src/write-hook.ts +1 -1
package/src/hashline/apply.ts
CHANGED
|
@@ -174,11 +174,13 @@ export function planEdit(
|
|
|
174
174
|
skipBoundaryDedup?: boolean;
|
|
175
175
|
strictBoundaryDedup?: boolean;
|
|
176
176
|
signal?: AbortSignal;
|
|
177
|
+
baseFileLines?: string[];
|
|
177
178
|
},
|
|
178
179
|
): PlannedEdit {
|
|
179
180
|
const signal = options?.signal;
|
|
180
181
|
abortIf(signal);
|
|
181
|
-
const
|
|
182
|
+
const fileLines = options?.baseFileLines ?? buildIdx(content).fileLines;
|
|
183
|
+
const lineIndex = { fileLines };
|
|
182
184
|
const fileHashes = precomputedHashes;
|
|
183
185
|
const warnings: string[] = [];
|
|
184
186
|
|
package/src/hashline/resolve.ts
CHANGED
|
@@ -540,7 +540,7 @@ export function assertRangeServed(
|
|
|
540
540
|
? `\n\n[The range has ${rangeLength} lines; showing the first ${shownLength}. Call read()${filePath ? ` on ${filePath}` : ""} with offset=${startLine + shownLength} to see the rest.]`
|
|
541
541
|
: "\n\nRetry with the fresh anchors above without a read.";
|
|
542
542
|
const message =
|
|
543
|
-
`[E_RANGE_STALE] ${mismatchText} what was shown.
|
|
543
|
+
`[E_RANGE_STALE] ${mismatchText} what was shown. Current range with fresh anchors:\n\n${rows.join("\n")}${capHint}`;
|
|
544
544
|
throw new RangeStaleError(message, shownHashes, shownMap);
|
|
545
545
|
}
|
|
546
546
|
|
package/src/insert.ts
CHANGED
|
@@ -3,7 +3,7 @@ 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
|
+
import { batchMemberFor, ensureBatchBase, executeBatchMember, noteBatchFailure, suffixPoisonCause } from "./batch";
|
|
7
7
|
import { readNormFile, type NormFile } from "./file-reader";
|
|
8
8
|
import { MAX_HASH_LINES, parseHashRef, resEdit, resolveAnchorLine, type Anchor, type HEdit } from "./hashline";
|
|
9
9
|
import { stripAnchorRow } from "./hashline/resolve";
|
|
@@ -193,6 +193,7 @@ export function buildInsertToolDef(flags: EditToolFlags = DEFAULT_EDIT_FLAGS): I
|
|
|
193
193
|
}).catch((error: unknown) => {
|
|
194
194
|
const member = batchMemberFor(_toolCallId);
|
|
195
195
|
if (member) noteBatchFailure(member, error);
|
|
196
|
+
else suffixPoisonCause(_toolCallId, error);
|
|
196
197
|
throw error;
|
|
197
198
|
});
|
|
198
199
|
let ref: Anchor;
|
|
@@ -207,51 +208,54 @@ export function buildInsertToolDef(flags: EditToolFlags = DEFAULT_EDIT_FLAGS): I
|
|
|
207
208
|
}
|
|
208
209
|
return queuedEdit(targetPath, ctx.cwd, signal, async (absolutePath, mutationTargetPath) => {
|
|
209
210
|
const member = batchMemberFor(_toolCallId);
|
|
211
|
+
if (member) {
|
|
212
|
+
const base = await ensureBatchBase({ member, targetPath, mutationTargetPath, cwd: ctx.cwd, signal });
|
|
213
|
+
const basePreload = { normalized: base.content, fileHashes: base.hashes } as NormFile;
|
|
214
|
+
const built = buildInsertEdit(req, basePreload, ref, targetPath);
|
|
215
|
+
let hedit: HEdit;
|
|
216
|
+
const resWarnings: string[] = [];
|
|
217
|
+
try {
|
|
218
|
+
hedit = resEdit(built.editParams, resWarnings);
|
|
219
|
+
} catch (error) {
|
|
220
|
+
noteBatchFailure(member, error);
|
|
221
|
+
throw error;
|
|
222
|
+
}
|
|
223
|
+
return executeBatchMember({
|
|
224
|
+
kind: "insert",
|
|
225
|
+
member,
|
|
226
|
+
targetPath,
|
|
227
|
+
mutationTargetPath,
|
|
228
|
+
cwd: ctx.cwd,
|
|
229
|
+
signal,
|
|
230
|
+
hedit,
|
|
231
|
+
extraWarnings: [...anchorWarnings, ...insertWarnings, ...resWarnings],
|
|
232
|
+
skipBoundaryDedup: true,
|
|
233
|
+
strictBoundaryDedup: false,
|
|
234
|
+
foldedLines: built.anchorLine === undefined ? 0 : 1,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
210
237
|
const preload = await readNormFile(targetPath, ctx.cwd, {
|
|
211
238
|
signal,
|
|
212
239
|
accessMode: constants.R_OK | constants.W_OK,
|
|
213
240
|
maxLines: MAX_HASH_LINES,
|
|
214
241
|
});
|
|
215
242
|
const { editParams, anchorLine } = buildInsertEdit(req, preload, ref, targetPath);
|
|
216
|
-
|
|
217
|
-
|
|
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,
|
|
247
|
-
mutationTargetPath,
|
|
248
|
-
cwd: ctx.cwd,
|
|
243
|
+
const pipe = await execPipeline(targetPath, editParams, ctx.cwd, {
|
|
244
|
+
accessMode: constants.R_OK | constants.W_OK,
|
|
249
245
|
signal,
|
|
250
|
-
|
|
251
|
-
extraWarnings: [...anchorWarnings, ...insertWarnings, ...resWarnings],
|
|
246
|
+
preloadedNorm: preload,
|
|
252
247
|
skipBoundaryDedup: true,
|
|
253
|
-
|
|
254
|
-
|
|
248
|
+
});
|
|
249
|
+
return commitEdit(pipe, {
|
|
250
|
+
path: pipe.path,
|
|
251
|
+
absolutePath,
|
|
252
|
+
mutationTargetPath,
|
|
253
|
+
signal,
|
|
254
|
+
verb: "inserted",
|
|
255
|
+
noopNoun: "Insertion",
|
|
256
|
+
foldedAnchorLines: anchorLine === undefined ? 0 : 1,
|
|
257
|
+
prefixWarnings: [...anchorWarnings, ...insertWarnings],
|
|
258
|
+
onApplied: () => clearBoundaryBypass(mutationTargetPath),
|
|
255
259
|
});
|
|
256
260
|
});
|
|
257
261
|
},
|
package/src/replace.ts
CHANGED
|
@@ -34,7 +34,7 @@ import { toCwd } from "./paths";
|
|
|
34
34
|
import { noopPayloadKey, markBoundaryNoop, consumeBoundaryBypass, clearBoundaryBypass } from "./boundary-bypass";
|
|
35
35
|
import { queuedEdit, editToolBase, editRenderCallWrapper, editRenderResultWrapper, resolveEditTargetWithRequirement, throwIfStrictInput, getBoundaryDedupMode, withReplacePrompts, DEFAULT_EDIT_FLAGS, type EditToolFlags } from "./edit-common";
|
|
36
36
|
import { commitEdit } from "./commit";
|
|
37
|
-
import { batchMemberFor, executeBatchMember, noteBatchFailure } from "./batch";
|
|
37
|
+
import { batchMemberFor, executeBatchMember, noteBatchFailure, suffixPoisonCause } from "./batch";
|
|
38
38
|
|
|
39
39
|
export { editToolSchema, type ReqParams, assertReq };
|
|
40
40
|
|
|
@@ -286,6 +286,7 @@ export function buildToolDef(flags: EditToolFlags = DEFAULT_EDIT_FLAGS): ToolDef
|
|
|
286
286
|
}).catch((error: unknown) => {
|
|
287
287
|
const member = batchMemberFor(_toolCallId);
|
|
288
288
|
if (member) noteBatchFailure(member, error);
|
|
289
|
+
else suffixPoisonCause(_toolCallId, error);
|
|
289
290
|
throw error;
|
|
290
291
|
});
|
|
291
292
|
return queuedEdit(targetPath, ctx.cwd, signal, async (absolutePath, mutationTargetPath) => {
|
|
@@ -296,31 +297,38 @@ export function buildToolDef(flags: EditToolFlags = DEFAULT_EDIT_FLAGS): ToolDef
|
|
|
296
297
|
const strictBoundaryDedup = dedupMode === "strict" && !boundaryBypass;
|
|
297
298
|
const member = batchMemberFor(_toolCallId);
|
|
298
299
|
if (!member) {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
300
|
+
try {
|
|
301
|
+
const pipe = await execPipeline(
|
|
302
|
+
targetPath,
|
|
303
|
+
normalizedParams,
|
|
304
|
+
ctx.cwd,
|
|
305
|
+
{ accessMode: constants.R_OK | constants.W_OK, signal, skipBoundaryDedup: boundaryBypass },
|
|
306
|
+
);
|
|
307
|
+
const appliedWarnings = boundaryBypass
|
|
308
|
+
? ["[W_BOUNDARY_BYPASS] Boundary dedup was off for this call and is back on."]
|
|
309
|
+
: [];
|
|
310
|
+
return await commitEdit(pipe, {
|
|
311
|
+
path: pipe.path,
|
|
312
|
+
absolutePath,
|
|
313
|
+
mutationTargetPath,
|
|
314
|
+
editAnchors: [normalizedParams.remove_from, normalizedParams.remove_to],
|
|
315
|
+
signal,
|
|
316
|
+
appliedWarnings,
|
|
317
|
+
onApplied: () => { if (dedupOn) clearBoundaryBypass(mutationTargetPath); },
|
|
318
|
+
onNoopDedup: dedupOn ? () => markBoundaryNoop(mutationTargetPath, noopPayload) : undefined,
|
|
319
|
+
});
|
|
320
|
+
} catch (error) {
|
|
321
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
322
|
+
if (boundaryBypass && !detail.includes("File was written;")) markBoundaryNoop(mutationTargetPath, noopPayload);
|
|
323
|
+
throw error;
|
|
324
|
+
}
|
|
318
325
|
}
|
|
319
326
|
let built: { edit: HEdit; warnings: string[] };
|
|
320
327
|
try {
|
|
321
328
|
built = buildReplaceHEdit(normalizedParams);
|
|
322
329
|
} catch (error) {
|
|
323
330
|
noteBatchFailure(member, error);
|
|
331
|
+
if (boundaryBypass) markBoundaryNoop(mutationTargetPath, noopPayload);
|
|
324
332
|
throw error;
|
|
325
333
|
}
|
|
326
334
|
const appliedWarnings = boundaryBypass
|
|
@@ -338,6 +346,7 @@ export function buildToolDef(flags: EditToolFlags = DEFAULT_EDIT_FLAGS): ToolDef
|
|
|
338
346
|
skipBoundaryDedup: boundaryBypass,
|
|
339
347
|
strictBoundaryDedup,
|
|
340
348
|
noopPayload,
|
|
349
|
+
bypassConsumed: boundaryBypass,
|
|
341
350
|
});
|
|
342
351
|
});
|
|
343
352
|
},
|
package/src/validation.ts
CHANGED
|
@@ -32,10 +32,10 @@ export function valKind(file: LFile, path: string): asserts file is { kind: "tex
|
|
|
32
32
|
throw new Error(`[E_NOT_TEXT] Path is a directory: ${path}. Use ls to inspect directories.`);
|
|
33
33
|
}
|
|
34
34
|
if (file.kind === "binary") {
|
|
35
|
-
throw new Error(`[E_NOT_TEXT] Path is a binary file: ${path} (${file.description})
|
|
35
|
+
throw new Error(`[E_NOT_TEXT] Path is a binary file: ${path} (${file.description}).`);
|
|
36
36
|
}
|
|
37
37
|
if (file.kind === "image") {
|
|
38
|
-
throw new Error(`[E_NOT_TEXT] Path is an image file: ${path}
|
|
38
|
+
throw new Error(`[E_NOT_TEXT] Path is an image file: ${path}.`);
|
|
39
39
|
}
|
|
40
40
|
if (file.kind === "too_large") {
|
|
41
41
|
throw new Error(
|
package/src/write-hook.ts
CHANGED
|
@@ -31,7 +31,7 @@ export async function servedHashEchoDenial(rawPath: string, content: string, cwd
|
|
|
31
31
|
if (!served || served.size === 0) return undefined;
|
|
32
32
|
const echo = findServedHashEcho(content, served);
|
|
33
33
|
if (!echo) return undefined;
|
|
34
|
-
return `[E_WRITE_HASH_ECHO] Refused write to ${rawPath}: line ${echo.line} begins with the exact ${echo.hash}${HASH_SEP} anchor served for this file. Remove the copied anchors and retry
|
|
34
|
+
return `[E_WRITE_HASH_ECHO] Refused write to ${rawPath}: line ${echo.line} begins with the exact ${echo.hash}${HASH_SEP} anchor served for this file. Remove the copied anchors and retry.`;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
export function registerWriteHook(pi: ExtensionAPI): void {
|