pi-hashline-edit-pro 4.3.9 → 4.4.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 +5 -7
- package/index.ts +1 -3
- package/package.json +1 -1
- package/prompts/insert.md +1 -1
- package/prompts/replace-guidelines.md +0 -1
- package/src/batch.ts +6 -49
- package/src/commit.ts +1 -30
- package/src/config-ui.ts +1 -2
- package/src/config.ts +0 -24
- package/src/constants.ts +0 -2
- package/src/edit-common.ts +1 -18
- package/src/hashline/apply.ts +4 -69
- package/src/hashline/index.ts +0 -3
- package/src/hashline/resolve.ts +4 -159
- package/src/insert.ts +0 -4
- package/src/replace-diff.ts +7 -44
- package/src/replace-render.ts +0 -2
- package/src/replace-response.ts +5 -86
- package/src/replace.ts +6 -26
package/src/replace-response.ts
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
import { formatSize, DEFAULT_MAX_BYTES } from "@earendil-works/pi-coding-agent";
|
|
2
1
|
import type { NEdit } from "./hashline";
|
|
3
|
-
import { HASH_SEP } from "./hashline";
|
|
4
2
|
import type { ReplaceDetails } from "./replace";
|
|
5
|
-
import { genDiff, genPatch,
|
|
3
|
+
import { genDiff, genPatch, type DiffSpan } from "./replace-diff";
|
|
6
4
|
import { visLines, clipLine } from "./utils";
|
|
7
|
-
import { DEDUP_ANCHOR } from "./constants";
|
|
8
5
|
|
|
9
6
|
export type TResult = {
|
|
10
7
|
content: Array<{ type: "text"; text: string }>;
|
|
@@ -37,7 +34,6 @@ export interface NoopInput {
|
|
|
37
34
|
snapshotId?: string;
|
|
38
35
|
editMeta: RMeta;
|
|
39
36
|
warnings: string[] | undefined;
|
|
40
|
-
boundaryRemovedLines?: number;
|
|
41
37
|
}
|
|
42
38
|
|
|
43
39
|
export interface SuccessInput {
|
|
@@ -49,8 +45,6 @@ export interface SuccessInput {
|
|
|
49
45
|
warnings: string[] | undefined;
|
|
50
46
|
snapshotId?: string;
|
|
51
47
|
editMeta: RMeta;
|
|
52
|
-
boundaryDedupAbove?: string[];
|
|
53
|
-
boundaryDedupBelow?: string[];
|
|
54
48
|
spans?: DiffSpan[];
|
|
55
49
|
}
|
|
56
50
|
|
|
@@ -98,18 +92,12 @@ export function buildNoop(input: NoopInput, noopNoun = "Replacement"): TResult {
|
|
|
98
92
|
snapshotId,
|
|
99
93
|
editMeta,
|
|
100
94
|
warnings,
|
|
101
|
-
boundaryRemovedLines,
|
|
102
95
|
} = input;
|
|
103
96
|
|
|
104
97
|
const noopDetailsText = noopEdit
|
|
105
98
|
? `${noopNoun} for ${noopEdit.loc} is identical to current content:\n ${noopEdit.loc}: ${clipLine(noopEdit.currentContent)}`
|
|
106
99
|
: "The edit produced identical content.";
|
|
107
|
-
const
|
|
108
|
-
boundaryRemovedLines !== undefined && boundaryRemovedLines > 0
|
|
109
|
-
? `\nBoundary dedup removed ${boundaryRemovedLines} line(s) that duplicated adjacent lines.`
|
|
110
|
-
: "";
|
|
111
|
-
|
|
112
|
-
const text = `No changes made to ${path}\nClassification: noop\n${noopDetailsText}${dedupNote}${warnBlock(warnings)}`;
|
|
100
|
+
const text = `No changes made to ${path}\nClassification: noop\n${noopDetailsText}${warnBlock(warnings)}`;
|
|
113
101
|
|
|
114
102
|
const metrics = buildMetrics({
|
|
115
103
|
classification: "noop",
|
|
@@ -131,80 +119,11 @@ export function buildNoop(input: NoopInput, noopNoun = "Replacement"): TResult {
|
|
|
131
119
|
},
|
|
132
120
|
};
|
|
133
121
|
}
|
|
134
|
-
export { fmtDedupRow };
|
|
135
|
-
|
|
136
|
-
export function isDedupRow(line: string): boolean {
|
|
137
|
-
return line.startsWith(`${DEDUP_ANCHOR}${HASH_SEP}`);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export function isChangeRow(line: string): boolean {
|
|
141
|
-
return line.startsWith("+") || line.startsWith("-");
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function capDedupRows(rows: string[], budget: number): { rows: string[]; bytes: number; omitted: number } {
|
|
145
|
-
const kept: string[] = [];
|
|
146
|
-
let bytes = 0;
|
|
147
|
-
for (const row of rows) {
|
|
148
|
-
const rowBytes = Buffer.byteLength(row, "utf-8") + 1;
|
|
149
|
-
if (bytes + rowBytes > budget) break;
|
|
150
|
-
kept.push(row);
|
|
151
|
-
bytes += rowBytes;
|
|
152
|
-
}
|
|
153
|
-
return { rows: kept, bytes, omitted: rows.length - kept.length };
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function dedupOmissionNote(omitted: number, maxBytes: number): string {
|
|
157
|
-
return fmtDedupRow(`[${omitted} line(s) not shown again; output capped at ${formatSize(maxBytes)}.]`);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
export function withDedupRows(diff: string, lineNumbers: (number | undefined)[], above: string[] | undefined, below: string[] | undefined, maxBytes = DEFAULT_MAX_BYTES): { diff: string; lineNumbers: (number | undefined)[] } {
|
|
161
|
-
const topAll = (above ?? []).map(fmtDedupRow);
|
|
162
|
-
const bottomAll = (below ?? []).map(fmtDedupRow);
|
|
163
|
-
if (topAll.length === 0 && bottomAll.length === 0) return { diff, lineNumbers };
|
|
164
|
-
const usedBytes = Buffer.byteLength(diff, "utf-8");
|
|
165
|
-
const fullBudget = Math.max(0, maxBytes - usedBytes);
|
|
166
|
-
const initialTop = capDedupRows(topAll, fullBudget);
|
|
167
|
-
const initialBottom = capDedupRows(bottomAll, Math.max(0, fullBudget - initialTop.bytes));
|
|
168
|
-
const anyOmitted = initialTop.omitted + initialBottom.omitted > 0;
|
|
169
|
-
const reserved = anyOmitted ? Buffer.byteLength(dedupOmissionNote(topAll.length + bottomAll.length, maxBytes), "utf-8") + 1 : 0;
|
|
170
|
-
const budget = Math.max(0, fullBudget - reserved);
|
|
171
|
-
const topCapped = anyOmitted ? capDedupRows(topAll, budget) : initialTop;
|
|
172
|
-
const bottomCapped = anyOmitted ? capDedupRows(bottomAll, Math.max(0, budget - topCapped.bytes)) : initialBottom;
|
|
173
|
-
const top = topCapped.rows;
|
|
174
|
-
const bottom = [...bottomCapped.rows];
|
|
175
|
-
const omitted = topCapped.omitted + bottomCapped.omitted;
|
|
176
|
-
if (omitted > 0) {
|
|
177
|
-
const note = dedupOmissionNote(omitted, maxBytes);
|
|
178
|
-
if (usedBytes + topCapped.bytes + bottomCapped.bytes + Buffer.byteLength(note, "utf-8") + 1 <= maxBytes) bottom.push(note);
|
|
179
|
-
}
|
|
180
|
-
if (top.length === 0 && bottom.length === 0) return { diff, lineNumbers };
|
|
181
|
-
if (diff.length === 0) return { diff: [...top, ...bottom].join("\n"), lineNumbers: [...lineNumbers, ...[...top, ...bottom].map(() => undefined)] };
|
|
182
|
-
const lines = diff.split("\n");
|
|
183
|
-
let first = -1;
|
|
184
|
-
let last = -1;
|
|
185
|
-
for (let i = 0; i < lines.length; i++) {
|
|
186
|
-
if (isChangeRow(lines[i]!)) {
|
|
187
|
-
if (first < 0) first = i;
|
|
188
|
-
last = i;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
if (first < 0) return { diff: `${diff}\n${[...top, ...bottom].join("\n")}`, lineNumbers: [...lineNumbers, ...[...top, ...bottom].map(() => undefined)] };
|
|
192
|
-
const out = [...lines];
|
|
193
|
-
const nums = [...lineNumbers];
|
|
194
|
-
out.splice(last + 1, 0, ...bottom);
|
|
195
|
-
nums.splice(last + 1, 0, ...bottom.map(() => undefined));
|
|
196
|
-
out.splice(first, 0, ...top);
|
|
197
|
-
nums.splice(first, 0, ...top.map(() => undefined));
|
|
198
|
-
return { diff: out.join("\n"), lineNumbers: nums };
|
|
199
|
-
}
|
|
200
122
|
|
|
201
123
|
export function buildChanged(input: SuccessInput, verb = "replaced", diffContextLines = 1): TResult {
|
|
202
|
-
const { path, result, warnings, snapshotId, originalNormalized, originalHashes, editMeta, resultHashes,
|
|
124
|
+
const { path, result, warnings, snapshotId, originalNormalized, originalHashes, editMeta, resultHashes, spans } = input;
|
|
203
125
|
const resultLines = visLines(result);
|
|
204
|
-
const
|
|
205
|
-
const diffResult = baseDiff.spanDedupEmitted
|
|
206
|
-
? baseDiff
|
|
207
|
-
: withDedupRows(baseDiff.diff, baseDiff.lineNumbers, boundaryDedupAbove, boundaryDedupBelow);
|
|
126
|
+
const diffResult = genDiff(originalNormalized, result, diffContextLines, resultHashes, originalHashes, undefined, spans);
|
|
208
127
|
const addedLines = editMeta.addedLines;
|
|
209
128
|
const removedLines = editMeta.removedLines;
|
|
210
129
|
const warningsBlock = warnBlock(warnings);
|
|
@@ -237,7 +156,7 @@ export function buildChanged(input: SuccessInput, verb = "replaced", diffContext
|
|
|
237
156
|
patch: patchResult.patch,
|
|
238
157
|
...(patchResult.truncated ? { patchTruncated: true as const } : {}),
|
|
239
158
|
firstChangedLine:
|
|
240
|
-
editMeta.firstChangedLine ??
|
|
159
|
+
editMeta.firstChangedLine ?? diffResult.firstChangedLine,
|
|
241
160
|
snapshotId,
|
|
242
161
|
metrics,
|
|
243
162
|
diffLineNumbers: diffResult.lineNumbers.map((line) => line ?? null),
|
package/src/replace.ts
CHANGED
|
@@ -22,7 +22,7 @@ import { applyEdit,
|
|
|
22
22
|
type HEdit,
|
|
23
23
|
type NEdit,
|
|
24
24
|
} from "./hashline";
|
|
25
|
-
import {
|
|
25
|
+
import { type RMetrics } from "./replace-response";
|
|
26
26
|
import {
|
|
27
27
|
type RPreview,
|
|
28
28
|
type RRState,
|
|
@@ -31,8 +31,8 @@ import { loadHashStore, type HashStore } from "./hash-store";
|
|
|
31
31
|
import { adoptAnchors, servedForPath, withAnchorSession } from "./anchor-registry";
|
|
32
32
|
import { resolveTarget } from "./fs-write";
|
|
33
33
|
import { toCwd, toDisplayPath } from "./paths";
|
|
34
|
-
import { queuedEdit, editToolBase, editRenderCallWrapper, editRenderResultWrapper, resolveEditTargetWithRequirement, throwIfStrictInput,
|
|
35
|
-
import { commitEdit
|
|
34
|
+
import { queuedEdit, editToolBase, editRenderCallWrapper, editRenderResultWrapper, resolveEditTargetWithRequirement, throwIfStrictInput, withReplacePrompts, DEFAULT_EDIT_FLAGS, type EditToolFlags } from "./edit-common";
|
|
35
|
+
import { commitEdit } from "./commit";
|
|
36
36
|
import { batchMemberFor, executeBatchMember, noteBatchFailure } from "./batch";
|
|
37
37
|
|
|
38
38
|
export { editToolSchema, type ReqParams, assertReq };
|
|
@@ -65,10 +65,6 @@ export interface PipelineResult {
|
|
|
65
65
|
resultHashes: string[];
|
|
66
66
|
totalAddedLines: number;
|
|
67
67
|
totalRemovedLines: number;
|
|
68
|
-
boundaryRemovedLines: number;
|
|
69
|
-
boundaryRemovedLineTexts: string[];
|
|
70
|
-
boundaryDedupAbove: string[];
|
|
71
|
-
boundaryDedupBelow: string[];
|
|
72
68
|
identity: FileIdentity;
|
|
73
69
|
spans?: DiffSpan[];
|
|
74
70
|
}
|
|
@@ -79,7 +75,6 @@ export interface ExecPipelineOptions {
|
|
|
79
75
|
signal?: AbortSignal;
|
|
80
76
|
store?: HashStore;
|
|
81
77
|
noPersist?: boolean;
|
|
82
|
-
skipBoundaryDedup?: boolean;
|
|
83
78
|
preloadedNorm?: NormFile;
|
|
84
79
|
}
|
|
85
80
|
|
|
@@ -109,13 +104,12 @@ function countLineChanges(
|
|
|
109
104
|
edit: HEdit,
|
|
110
105
|
originalHashes: string[],
|
|
111
106
|
isNoop: boolean,
|
|
112
|
-
removedAutoFixes: number,
|
|
113
107
|
): { totalAddedLines: number; totalRemovedLines: number } {
|
|
114
108
|
if (isNoop) return { totalAddedLines: 0, totalRemovedLines: 0 };
|
|
115
109
|
const span = hashSpan(originalHashes, edit.hash_bounds[0].hash, edit.hash_bounds[1].hash);
|
|
116
110
|
const totalRemovedLines = span ? span[1] - span[0] + 1 : 0;
|
|
117
111
|
return {
|
|
118
|
-
totalAddedLines:
|
|
112
|
+
totalAddedLines: edit.content_lines.length,
|
|
119
113
|
totalRemovedLines,
|
|
120
114
|
};
|
|
121
115
|
}
|
|
@@ -149,9 +143,6 @@ export async function execPipeline(
|
|
|
149
143
|
);
|
|
150
144
|
const displayPath = toDisplayPath(cwd, absolutePath, targetPath);
|
|
151
145
|
|
|
152
|
-
const dedupMode = await getBoundaryDedupMode();
|
|
153
|
-
const effectiveSkipBoundaryDedup = options?.skipBoundaryDedup === true || dedupMode === "off";
|
|
154
|
-
const strictBoundaryDedup = options?.skipBoundaryDedup !== true && dedupMode === "strict";
|
|
155
146
|
let anchorResult: ReturnType<typeof applyEdit>;
|
|
156
147
|
try {
|
|
157
148
|
anchorResult = applyEdit(
|
|
@@ -161,8 +152,6 @@ export async function execPipeline(
|
|
|
161
152
|
originalHashes,
|
|
162
153
|
displayPath,
|
|
163
154
|
served,
|
|
164
|
-
effectiveSkipBoundaryDedup,
|
|
165
|
-
strictBoundaryDedup,
|
|
166
155
|
);
|
|
167
156
|
} catch (error) {
|
|
168
157
|
await noteAnchorError(absolutePath, error, options?.noPersist);
|
|
@@ -181,10 +170,9 @@ export async function execPipeline(
|
|
|
181
170
|
const warnings = [...editWarnings, ...(anchorResult.warnings ?? [])];
|
|
182
171
|
await throwIfStrictInput(warnings);
|
|
183
172
|
const { totalAddedLines, totalRemovedLines } = countLineChanges(
|
|
184
|
-
edit, originalHashes, isNoop,
|
|
173
|
+
edit, originalHashes, isNoop,
|
|
185
174
|
);
|
|
186
175
|
|
|
187
|
-
const sortedFixes = dedupRowsFromFixes(anchorResult.autoFixes ?? []);
|
|
188
176
|
const pipeSpan = isNoop ? undefined : spanForEdit(originalHashes, edit.hash_bounds[0].hash, edit.hash_bounds[1].hash, result);
|
|
189
177
|
const pipeSpans = pipeSpan ? [pipeSpan] : undefined;
|
|
190
178
|
return {
|
|
@@ -202,10 +190,6 @@ export async function execPipeline(
|
|
|
202
190
|
originalHashes,
|
|
203
191
|
totalAddedLines,
|
|
204
192
|
totalRemovedLines,
|
|
205
|
-
boundaryRemovedLines: anchorResult.autoFixes?.length ?? 0,
|
|
206
|
-
boundaryRemovedLineTexts: sortedFixes.removedTexts,
|
|
207
|
-
boundaryDedupAbove: sortedFixes.above,
|
|
208
|
-
boundaryDedupBelow: sortedFixes.below,
|
|
209
193
|
identity,
|
|
210
194
|
...(pipeSpans ? { spans: pipeSpans } : {}),
|
|
211
195
|
};
|
|
@@ -219,7 +203,7 @@ export function previewFromPipe(pipe: PipelineResult): RPreview {
|
|
|
219
203
|
};
|
|
220
204
|
}
|
|
221
205
|
const base = genDiff(pipe.originalNormalized, pipe.result, 4, pipe.resultHashes, pipe.originalHashes, undefined, pipe.spans);
|
|
222
|
-
return { diff:
|
|
206
|
+
return { diff: base.diff, path: pipe.path };
|
|
223
207
|
}
|
|
224
208
|
export function previewError(error: unknown): RPreview {
|
|
225
209
|
return { error: error instanceof Error ? error.message : String(error) };
|
|
@@ -290,8 +274,6 @@ export function buildToolDef(flags: EditToolFlags = DEFAULT_EDIT_FLAGS): ToolDef
|
|
|
290
274
|
throw error;
|
|
291
275
|
});
|
|
292
276
|
return queuedEdit(targetPath, ctx.cwd, signal, async (absolutePath, mutationTargetPath) => {
|
|
293
|
-
const dedupMode = await getBoundaryDedupMode();
|
|
294
|
-
const strictBoundaryDedup = dedupMode === "strict";
|
|
295
277
|
const member = batchMemberFor(_toolCallId);
|
|
296
278
|
if (!member) {
|
|
297
279
|
const pipe = await execPipeline(
|
|
@@ -324,8 +306,6 @@ export function buildToolDef(flags: EditToolFlags = DEFAULT_EDIT_FLAGS): ToolDef
|
|
|
324
306
|
signal,
|
|
325
307
|
hedit: built.edit,
|
|
326
308
|
extraWarnings: built.warnings,
|
|
327
|
-
skipBoundaryDedup: dedupMode === "off",
|
|
328
|
-
strictBoundaryDedup,
|
|
329
309
|
});
|
|
330
310
|
});
|
|
331
311
|
});
|