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.
@@ -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, fmtDedupRow, type DiffSpan } from "./replace-diff";
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 dedupNote =
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, boundaryDedupAbove, boundaryDedupBelow, spans } = input;
124
+ const { path, result, warnings, snapshotId, originalNormalized, originalHashes, editMeta, resultHashes, spans } = input;
203
125
  const resultLines = visLines(result);
204
- const baseDiff = genDiff(originalNormalized, result, diffContextLines, resultHashes, originalHashes, undefined, spans);
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 ?? baseDiff.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 { withDedupRows, type RMetrics } from "./replace-response";
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, getBoundaryDedupMode, withReplacePrompts, DEFAULT_EDIT_FLAGS, type EditToolFlags } from "./edit-common";
35
- import { commitEdit, dedupRowsFromFixes } from "./commit";
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: Math.max(0, edit.content_lines.length - removedAutoFixes),
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, anchorResult.autoFixes?.length ?? 0,
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: withDedupRows(base.diff, base.lineNumbers, pipe.boundaryDedupAbove, pipe.boundaryDedupBelow).diff, path: pipe.path };
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
  });