pi-hashline-edit-pro 4.0.1 → 4.2.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 +17 -12
- package/index.ts +70 -23
- package/package.json +1 -1
- package/prompts/insert-guidelines.md +2 -1
- package/prompts/insert.md +1 -1
- package/prompts/read-guidelines.md +1 -0
- package/prompts/read.md +1 -1
- package/prompts/replace-guidelines.md +5 -5
- package/prompts/replace.md +1 -1
- package/src/anchor-registry.ts +11 -8
- package/src/batch.ts +614 -0
- package/src/boundary-bypass.ts +6 -0
- package/src/commit.ts +1 -1
- package/src/config-ui.ts +108 -0
- package/src/config.ts +48 -1
- package/src/edit-common.ts +115 -1
- package/src/fs-write.ts +6 -0
- package/src/grep.ts +84 -1
- package/src/hashline/alphabet.ts +1 -1
- package/src/hashline/apply.ts +128 -84
- package/src/hashline/index.ts +3 -0
- package/src/hashline/resolve.ts +5 -4
- package/src/insert.ts +127 -74
- package/src/payload-contract.ts +50 -2
- package/src/read.ts +10 -11
- package/src/replace-render.ts +2 -2
- package/src/replace-response.ts +3 -1
- package/src/replace.ts +94 -37
package/src/read.ts
CHANGED
|
@@ -15,18 +15,16 @@ import { lineHashes, fmtRegion, fmtRow, HASH_SEP, MAX_HASH_LINES } from "./hashl
|
|
|
15
15
|
import { toCwd } from "./paths";
|
|
16
16
|
import { abortIf, makePrepareArguments, numberedRead, visLines, splitLines } from "./utils";
|
|
17
17
|
import { loadP, loadGuide } from "./prompts";
|
|
18
|
+
import { withReadPrompts, DEFAULT_EDIT_FLAGS, type EditToolFlags } from "./edit-common";
|
|
18
19
|
import { valAccess } from "./validation";
|
|
19
20
|
import { markServed as markServedScoped } from "./anchor-registry";
|
|
20
21
|
import { buildServedMap } from "./served";
|
|
21
22
|
import { Text } from "@earendil-works/pi-tui";
|
|
22
23
|
const R_DESC = loadP("../prompts/read.md");
|
|
23
|
-
|
|
24
24
|
const R_SNIPPET = loadP("../prompts/read-snippet.md");
|
|
25
|
-
|
|
26
25
|
function readGuide(): string[] {
|
|
27
|
-
|
|
26
|
+
return loadGuide("../prompts/read-guidelines.md");
|
|
28
27
|
}
|
|
29
|
-
|
|
30
28
|
function normPosInt(
|
|
31
29
|
value: number | undefined,
|
|
32
30
|
name: "offset" | "limit",
|
|
@@ -166,13 +164,14 @@ export async function fmtReadPreview(
|
|
|
166
164
|
};
|
|
167
165
|
}
|
|
168
166
|
|
|
169
|
-
export function regRead(pi: ExtensionAPI): void {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
167
|
+
export function regRead(pi: ExtensionAPI, flags: EditToolFlags = DEFAULT_EDIT_FLAGS): void {
|
|
168
|
+
const prompted = withReadPrompts({ description: R_DESC, snippet: R_SNIPPET, guidelines: readGuide() }, flags);
|
|
169
|
+
pi.registerTool({
|
|
170
|
+
name: "read",
|
|
171
|
+
label: "Read",
|
|
172
|
+
description: prompted.description,
|
|
173
|
+
promptSnippet: prompted.snippet,
|
|
174
|
+
promptGuidelines: prompted.guidelines,
|
|
176
175
|
prepareArguments: makePrepareArguments(),
|
|
177
176
|
parameters: Type.Object({
|
|
178
177
|
path: Type.String({
|
package/src/replace-render.ts
CHANGED
|
@@ -128,7 +128,7 @@ export function isApplied(
|
|
|
128
128
|
|
|
129
129
|
const RESULT_PREVIEW_LINES = 16;
|
|
130
130
|
|
|
131
|
-
function expandHint(): string {
|
|
131
|
+
export function expandHint(): string {
|
|
132
132
|
try {
|
|
133
133
|
return keyHint("app.tools.expand", "to expand");
|
|
134
134
|
} catch {
|
|
@@ -165,7 +165,7 @@ export function buildAppliedText(
|
|
|
165
165
|
: "";
|
|
166
166
|
sections.push(`${diffSection}${hint}`);
|
|
167
167
|
}
|
|
168
|
-
const warnings = extractWarnings(text);
|
|
168
|
+
const warnings = details?.warnings?.length ? `Warnings:\n${details.warnings.join("\n")}` : extractWarnings(text);
|
|
169
169
|
if (warnings) sections.push(warnings);
|
|
170
170
|
return sections.length > 0 ? sections.join("\n\n") : undefined;
|
|
171
171
|
}
|
package/src/replace-response.ts
CHANGED
|
@@ -125,7 +125,8 @@ export function buildNoop(input: NoopInput, noopNoun = "Replacement"): TResult {
|
|
|
125
125
|
firstChangedLine: undefined,
|
|
126
126
|
snapshotId,
|
|
127
127
|
classification: "noop" as const,
|
|
128
|
-
|
|
128
|
+
metrics,
|
|
129
|
+
...(warnings?.length ? { warnings: [...warnings] } : {}),
|
|
129
130
|
},
|
|
130
131
|
};
|
|
131
132
|
}
|
|
@@ -210,6 +211,7 @@ export function buildChanged(input: SuccessInput, verb = "replaced"): TResult {
|
|
|
210
211
|
snapshotId,
|
|
211
212
|
metrics,
|
|
212
213
|
diffLineNumbers: diffResult.lineNumbers,
|
|
214
|
+
...(warnings?.length ? { warnings: [...warnings] } : {}),
|
|
213
215
|
},
|
|
214
216
|
};
|
|
215
217
|
}
|
package/src/replace.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
type LineEnding,
|
|
10
10
|
} from "./replace-diff";
|
|
11
11
|
import { readNormFile, type NormFile } from "./file-reader";
|
|
12
|
-
import { editToolSchema, type ReqParams, assertReq, normReq } from "./payload-contract";
|
|
12
|
+
import { editToolSchema, buildEditToolSchema, type ReqParams, assertReq, normReq } from "./payload-contract";
|
|
13
13
|
import { decodeStringArray } from "./utils";
|
|
14
14
|
import { loadP, loadGuide } from "./prompts";
|
|
15
15
|
import { type FileIdentity } from "./fs-write";
|
|
@@ -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,
|
|
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
|
|
|
@@ -46,6 +47,8 @@ export type ReplaceDetails = {
|
|
|
46
47
|
classification?: "noop";
|
|
47
48
|
metrics?: RMetrics;
|
|
48
49
|
diffLineNumbers?: (number|undefined)[];
|
|
50
|
+
warnings?: string[];
|
|
51
|
+
batch?: { id: number; size: number; last: boolean; total: number };
|
|
49
52
|
};
|
|
50
53
|
|
|
51
54
|
export interface PipelineResult {
|
|
@@ -111,13 +114,7 @@ function countLineChanges(
|
|
|
111
114
|
};
|
|
112
115
|
}
|
|
113
116
|
|
|
114
|
-
export
|
|
115
|
-
targetPath: string,
|
|
116
|
-
params: ReqParams,
|
|
117
|
-
cwd: string,
|
|
118
|
-
options?: ExecPipelineOptions,
|
|
119
|
-
): Promise<PipelineResult> {
|
|
120
|
-
|
|
117
|
+
export function buildReplaceHEdit(params: ReqParams): { edit: HEdit; warnings: string[] } {
|
|
121
118
|
const editWarnings: string[] = [];
|
|
122
119
|
let replacementLines = params.replacement_lines;
|
|
123
120
|
const expandedReplacement = decodeStringArray(replacementLines);
|
|
@@ -133,7 +130,17 @@ export async function execPipeline(
|
|
|
133
130
|
},
|
|
134
131
|
editWarnings,
|
|
135
132
|
);
|
|
133
|
+
return { edit, warnings: editWarnings };
|
|
134
|
+
}
|
|
136
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);
|
|
137
144
|
const hashStore = options?.store ?? await loadHashStore();
|
|
138
145
|
const preResolvedPath = await resolveTarget(toCwd(targetPath, cwd));
|
|
139
146
|
const served = servedForPath(preResolvedPath);
|
|
@@ -142,6 +149,9 @@ export async function execPipeline(
|
|
|
142
149
|
);
|
|
143
150
|
const displayPath = relative(cwd, absolutePath).replace(/\\/g, "/") || targetPath;
|
|
144
151
|
|
|
152
|
+
const dedupMode = await getBoundaryDedupMode();
|
|
153
|
+
const effectiveSkipBoundaryDedup = options?.skipBoundaryDedup === true || dedupMode === "off";
|
|
154
|
+
const strictBoundaryDedup = options?.skipBoundaryDedup !== true && dedupMode === "strict";
|
|
145
155
|
let anchorResult: ReturnType<typeof applyEdit>;
|
|
146
156
|
try {
|
|
147
157
|
anchorResult = applyEdit(
|
|
@@ -151,7 +161,8 @@ export async function execPipeline(
|
|
|
151
161
|
originalHashes,
|
|
152
162
|
displayPath,
|
|
153
163
|
served,
|
|
154
|
-
|
|
164
|
+
effectiveSkipBoundaryDedup,
|
|
165
|
+
strictBoundaryDedup,
|
|
155
166
|
);
|
|
156
167
|
} catch (error) {
|
|
157
168
|
await noteAnchorError(absolutePath, error, originalHashes, options?.noPersist);
|
|
@@ -168,6 +179,7 @@ export async function execPipeline(
|
|
|
168
179
|
hashes: originalHashes,
|
|
169
180
|
}, hashStore, false, true);
|
|
170
181
|
const warnings = [...editWarnings, ...(anchorResult.warnings ?? [])];
|
|
182
|
+
await throwIfStrictInput(warnings);
|
|
171
183
|
const { totalAddedLines, totalRemovedLines } = countLineChanges(
|
|
172
184
|
edit, originalHashes, isNoop, anchorResult.autoFixes?.length ?? 0,
|
|
173
185
|
);
|
|
@@ -220,7 +232,12 @@ export async function compPreview(
|
|
|
220
232
|
try {
|
|
221
233
|
const normalized = normReq(request);
|
|
222
234
|
assertReq(normalized);
|
|
223
|
-
const targetPath =
|
|
235
|
+
const targetPath = await resolveEditTargetWithRequirement({
|
|
236
|
+
removeFrom: (normalized as ReqParams).remove_from,
|
|
237
|
+
removeTo: (normalized as ReqParams).remove_to,
|
|
238
|
+
providedPath: (normalized as ReqParams).path,
|
|
239
|
+
cwd,
|
|
240
|
+
});
|
|
224
241
|
const pipe = await execPipeline(
|
|
225
242
|
targetPath,
|
|
226
243
|
normalized,
|
|
@@ -240,18 +257,20 @@ type ToolDef = ToolDefinition<
|
|
|
240
257
|
RRState
|
|
241
258
|
> & { renderShell?: "default" | "self" };
|
|
242
259
|
|
|
243
|
-
export function buildToolDef(): ToolDef {
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
260
|
+
export function buildToolDef(flags: EditToolFlags = DEFAULT_EDIT_FLAGS): ToolDef {
|
|
261
|
+
const prompted = withReplacePrompts({
|
|
262
|
+
description: loadP("../prompts/replace.md"),
|
|
263
|
+
snippet: loadP("../prompts/replace-snippet.md"),
|
|
264
|
+
guidelines: loadGuide("../prompts/replace-guidelines.md"),
|
|
265
|
+
}, flags);
|
|
266
|
+
const parameters = buildEditToolSchema(flags.requirePath);
|
|
248
267
|
return {
|
|
249
268
|
name: "replace",
|
|
250
269
|
label: "Replace",
|
|
251
|
-
description:
|
|
270
|
+
description: prompted.description,
|
|
252
271
|
parameters,
|
|
253
|
-
promptSnippet:
|
|
254
|
-
promptGuidelines:
|
|
272
|
+
promptSnippet: prompted.snippet,
|
|
273
|
+
promptGuidelines: prompted.guidelines,
|
|
255
274
|
...editToolBase,
|
|
256
275
|
renderCall: editRenderCallWrapper(compPreview),
|
|
257
276
|
renderResult: editRenderResultWrapper,
|
|
@@ -259,34 +278,72 @@ export function buildToolDef(): ToolDef {
|
|
|
259
278
|
const canonical = normReq(params);
|
|
260
279
|
assertReq(canonical);
|
|
261
280
|
const normalizedParams = canonical;
|
|
262
|
-
const targetPath =
|
|
281
|
+
const targetPath = await resolveEditTargetWithRequirement({
|
|
282
|
+
removeFrom: normalizedParams.remove_from,
|
|
283
|
+
removeTo: normalizedParams.remove_to,
|
|
284
|
+
providedPath: normalizedParams.path,
|
|
285
|
+
cwd: ctx.cwd,
|
|
286
|
+
}).catch((error: unknown) => {
|
|
287
|
+
const member = batchMemberFor(_toolCallId);
|
|
288
|
+
if (member) noteBatchFailure(member, error);
|
|
289
|
+
throw error;
|
|
290
|
+
});
|
|
263
291
|
return queuedEdit(targetPath, ctx.cwd, signal, async (absolutePath, mutationTargetPath) => {
|
|
292
|
+
const dedupMode = await getBoundaryDedupMode();
|
|
293
|
+
const dedupOn = dedupMode !== "off";
|
|
264
294
|
const noopPayload = noopPayloadKey(mutationTargetPath, normalizedParams.remove_from, normalizedParams.remove_to, normalizedParams.replacement_lines);
|
|
265
|
-
const boundaryBypass = consumeBoundaryBypass(mutationTargetPath, noopPayload);
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
295
|
+
const boundaryBypass = dedupOn ? consumeBoundaryBypass(mutationTargetPath, noopPayload) : false;
|
|
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
|
+
}
|
|
272
326
|
const appliedWarnings = boundaryBypass
|
|
273
327
|
? ["[W_BOUNDARY_BYPASS] Boundary dedup was off for this call and is back on."]
|
|
274
328
|
: [];
|
|
275
|
-
return
|
|
276
|
-
|
|
277
|
-
|
|
329
|
+
return executeBatchMember({
|
|
330
|
+
kind: "replace",
|
|
331
|
+
member,
|
|
332
|
+
targetPath,
|
|
278
333
|
mutationTargetPath,
|
|
279
|
-
|
|
334
|
+
cwd: ctx.cwd,
|
|
280
335
|
signal,
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
336
|
+
hedit: built.edit,
|
|
337
|
+
extraWarnings: [...built.warnings, ...appliedWarnings],
|
|
338
|
+
skipBoundaryDedup: boundaryBypass,
|
|
339
|
+
strictBoundaryDedup,
|
|
340
|
+
noopPayload,
|
|
284
341
|
});
|
|
285
342
|
});
|
|
286
343
|
},
|
|
287
344
|
};
|
|
288
345
|
}
|
|
289
346
|
|
|
290
|
-
export function regReplace(pi: ExtensionAPI): void {
|
|
291
|
-
pi.registerTool(buildToolDef());
|
|
347
|
+
export function regReplace(pi: ExtensionAPI, flags: EditToolFlags = DEFAULT_EDIT_FLAGS): void {
|
|
348
|
+
pi.registerTool(buildToolDef(flags));
|
|
292
349
|
}
|