pi-hashline-edit-pro 0.15.2 → 0.15.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-hashline-edit-pro",
3
- "version": "0.15.2",
3
+ "version": "0.15.3",
4
4
  "description": "Strict hashline read/replace tool for pi-coding-agent with hash-anchored edits (3-char, 18-bit, perfect hashing)",
5
5
  "main": "index.ts",
6
6
  "repository": {
@@ -45,10 +45,25 @@ export function genDiff(
45
45
  newContent: string,
46
46
  contextLines = 2,
47
47
  newContentHashes?: string[],
48
+ oldHashes?: string[],
48
49
  ): { diff: string; firstChangedLine: number | undefined } {
49
- const parts = Diff.diffLines(oldContent, newContent);
50
- const output: string[] = [];
50
+ // Annotate each line with its hash so Diff.diffLines can distinguish
51
+ // identical lines at different positions. The \0 separator cannot appear
52
+ // in valid text file content.
53
+ const oldLines = oldContent.split("\n");
54
+ const effectiveOldHashes = oldHashes ?? _lineHashesPure(oldContent);
55
+ const annotatedOld = oldLines
56
+ .map((line, i) => `${line}\0${effectiveOldHashes[i] ?? ""}`)
57
+ .join("\n");
58
+
59
+ const newLines = newContent.split("\n");
51
60
  const effectiveNewHashes = newContentHashes ?? _lineHashesPure(newContent);
61
+ const annotatedNew = newLines
62
+ .map((line, i) => `${line}\0${effectiveNewHashes[i] ?? ""}`)
63
+ .join("\n");
64
+
65
+ const parts = Diff.diffLines(annotatedOld, annotatedNew);
66
+ const output: string[] = [];
52
67
  let oldLineNum = 1;
53
68
  let newLineNum = 1;
54
69
  let lastWasChange = false;
@@ -58,18 +73,21 @@ export function genDiff(
58
73
  const part = parts[i]!;
59
74
  const raw = part.value.split("\n");
60
75
  if (raw[raw.length - 1] === "") raw.pop();
76
+ // Strip \0hash annotation from each line for display
77
+ const displayLines = raw.map((l) => {
78
+ const nullIdx = l.indexOf("\0");
79
+ return nullIdx >= 0 ? l.slice(0, nullIdx) : l;
80
+ });
61
81
 
62
82
  if (part.added || part.removed) {
63
83
  if (firstChangedLine === undefined) firstChangedLine = newLineNum;
64
- for (const line of raw) {
84
+ for (let k = 0; k < displayLines.length; k++) {
65
85
  if (part.added) {
66
86
  const hash = effectiveNewHashes[newLineNum - 1];
67
- output.push(fmtDiffLine("+", line, hash));
87
+ output.push(fmtDiffLine("+", displayLines[k]!, hash));
68
88
  newLineNum++;
69
89
  } else {
70
- output.push(
71
- fmtDiffLine("-", line, undefined),
72
- );
90
+ output.push(fmtDiffLine("-", displayLines[k]!, undefined));
73
91
  oldLineNum++;
74
92
  }
75
93
  }
@@ -80,18 +98,18 @@ export function genDiff(
80
98
  const nextPartIsChange =
81
99
  i < parts.length - 1 && (parts[i + 1]!.added || parts[i + 1]!.removed);
82
100
  if (lastWasChange || nextPartIsChange) {
83
- let linesToShow = raw;
101
+ let linesToShow = displayLines;
84
102
  let skipStart = 0;
85
103
  let skipEnd = 0;
86
104
  let skipMiddle = 0;
87
105
 
88
106
  if (!lastWasChange) {
89
- skipStart = Math.max(0, raw.length - contextLines);
90
- linesToShow = raw.slice(skipStart);
91
- } else if (nextPartIsChange && raw.length > contextLines * 2) {
92
- const tail = raw.slice(-contextLines);
93
- linesToShow = [...raw.slice(0, contextLines), "__ELLIPSIS__", ...tail];
94
- skipMiddle = raw.length - contextLines * 2;
107
+ skipStart = Math.max(0, displayLines.length - contextLines);
108
+ linesToShow = displayLines.slice(skipStart);
109
+ } else if (nextPartIsChange && displayLines.length > contextLines * 2) {
110
+ const tail = displayLines.slice(-contextLines);
111
+ linesToShow = [...displayLines.slice(0, contextLines), "__ELLIPSIS__", ...tail];
112
+ skipMiddle = displayLines.length - contextLines * 2;
95
113
  } else if (linesToShow.length > contextLines) {
96
114
  skipEnd = linesToShow.length - contextLines;
97
115
  linesToShow = linesToShow.slice(0, contextLines);
@@ -111,13 +129,12 @@ export function genDiff(
111
129
  }
112
130
  const hash = effectiveNewHashes[newLineNum - 1];
113
131
  output.push(fmtDiffLine(" ", line, hash));
114
-
115
132
  oldLineNum++;
116
133
  newLineNum++;
117
134
  }
118
135
  } else {
119
- oldLineNum += raw.length;
120
- newLineNum += raw.length;
136
+ oldLineNum += displayLines.length;
137
+ newLineNum += displayLines.length;
121
138
  }
122
139
  lastWasChange = false;
123
140
  }
@@ -238,6 +238,7 @@ export function buildToolDef(): ToolDef {
238
238
 
239
239
  const {
240
240
  originalNormalized,
241
+ originalHashes,
241
242
  result,
242
243
  bom,
243
244
  originalEnding,
@@ -294,13 +295,13 @@ export function buildToolDef(): ToolDef {
294
295
  const successInput = {
295
296
  path,
296
297
  originalNormalized,
298
+ originalHashes,
297
299
  result,
298
300
  resultHashes,
299
301
  warnings,
300
302
  snapshotId: updatedSnapshotId,
301
303
  editMeta,
302
304
  };
303
-
304
305
  return buildChanged(successInput);
305
306
  });
306
307
  },
@@ -40,13 +40,14 @@ export interface NoopInput {
40
40
  }
41
41
 
42
42
  export interface SuccessInput {
43
- path: string;
44
- originalNormalized: string;
45
- result: string;
46
- resultHashes: string[];
47
- warnings: string[] | undefined;
48
- snapshotId: string;
49
- editMeta: RMeta;
43
+ path: string;
44
+ originalNormalized: string;
45
+ originalHashes: string[];
46
+ result: string;
47
+ resultHashes: string[];
48
+ warnings: string[] | undefined;
49
+ snapshotId: string;
50
+ editMeta: RMeta;
50
51
  }
51
52
 
52
53
  function cntDiff(diff: string, marker: "+" | "-"): number {
@@ -139,39 +140,39 @@ export function buildNoop(input: NoopInput): TResult {
139
140
  }
140
141
 
141
142
  export function buildChanged(input: SuccessInput): TResult {
142
- const { path, result, warnings, snapshotId, originalNormalized, editMeta, resultHashes } = input;
143
-
144
- const resultLines = visLines(result);
145
- const diffResult = genDiff(originalNormalized, result, 2, resultHashes);
146
- const addedLines = cntDiff(diffResult.diff, "+");
147
- const removedLines = cntDiff(diffResult.diff, "-");
148
- const warningsBlock = warnBlock(warnings);
149
- const successPrefix = `Successfully replaced in ${path}.`;
150
- const text = resultLines.length === 0
151
- ? "File is empty. Use replace to insert content."
152
- : warningsBlock
153
- ? `${successPrefix}${warningsBlock}`
154
- : successPrefix;
155
-
156
- const metrics = buildM({
157
- classification: "applied",
158
- editsAttempted: editMeta.editsAttempted,
159
- noopEditsCount: editMeta.noopEditsCount,
160
- warningsCount: warnings?.length ?? 0,
161
- firstChangedLine: editMeta.firstChangedLine,
162
- lastChangedLine: editMeta.lastChangedLine,
163
- addedLines,
164
- removedLines,
165
- });
166
-
167
- return {
168
- content: [{ type: "text", text }],
169
- details: {
170
- diff: diffResult.diff,
171
- firstChangedLine:
172
- editMeta.firstChangedLine ?? diffResult.firstChangedLine,
173
- snapshotId,
174
- metrics,
175
- },
176
- };
143
+ const { path, result, warnings, snapshotId, originalNormalized, originalHashes, editMeta, resultHashes } = input;
144
+
145
+ const resultLines = visLines(result);
146
+ const diffResult = genDiff(originalNormalized, result, 2, resultHashes, originalHashes);
147
+ const addedLines = cntDiff(diffResult.diff, "+");
148
+ const removedLines = cntDiff(diffResult.diff, "-");
149
+ const warningsBlock = warnBlock(warnings);
150
+ const successPrefix = `Successfully replaced in ${path}.`;
151
+ const text = resultLines.length === 0
152
+ ? "File is empty. Use replace to insert content."
153
+ : warningsBlock
154
+ ? `${successPrefix}${warningsBlock}`
155
+ : successPrefix;
156
+
157
+ const metrics = buildM({
158
+ classification: "applied",
159
+ editsAttempted: editMeta.editsAttempted,
160
+ noopEditsCount: editMeta.noopEditsCount,
161
+ warningsCount: warnings?.length ?? 0,
162
+ firstChangedLine: editMeta.firstChangedLine,
163
+ lastChangedLine: editMeta.lastChangedLine,
164
+ addedLines,
165
+ removedLines,
166
+ });
167
+
168
+ return {
169
+ content: [{ type: "text", text }],
170
+ details: {
171
+ diff: diffResult.diff,
172
+ firstChangedLine:
173
+ editMeta.firstChangedLine ?? diffResult.firstChangedLine,
174
+ snapshotId,
175
+ metrics,
176
+ },
177
+ };
177
178
  }
package/src/replace.ts CHANGED
@@ -228,7 +228,7 @@ export async function compPreview(
228
228
  try {
229
229
  const normalized = normReq(request);
230
230
  assertReq(normalized);
231
- const { path, originalNormalized, result, resultHashes } = await execPipeline(
231
+ const { path, originalNormalized, originalHashes, result, resultHashes } = await execPipeline(
232
232
  normalized,
233
233
  cwd,
234
234
  constants.R_OK,
@@ -240,7 +240,7 @@ export async function compPreview(
240
240
  };
241
241
  }
242
242
 
243
- return { diff: genDiff(originalNormalized, result, 4, resultHashes).diff };
243
+ return { diff: genDiff(originalNormalized, result, 4, resultHashes, originalHashes).diff };
244
244
  } catch (error: unknown) {
245
245
  return { error: error instanceof Error ? error.message : String(error) };
246
246
  }
@@ -391,6 +391,7 @@ export function buildToolDef(): ToolDef {
391
391
 
392
392
  const {
393
393
  originalNormalized,
394
+ originalHashes,
394
395
  result,
395
396
  bom,
396
397
  originalEnding,
@@ -449,13 +450,13 @@ export function buildToolDef(): ToolDef {
449
450
  const successInput = {
450
451
  path,
451
452
  originalNormalized,
453
+ originalHashes,
452
454
  result,
453
455
  resultHashes,
454
456
  warnings,
455
457
  snapshotId: updatedSnapshotId,
456
458
  editMeta,
457
459
  };
458
-
459
460
  return buildChanged(successInput);
460
461
  });
461
462
  },