pi-edit-file 0.3.0 → 0.3.1
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 +1 -1
- package/package.json +1 -1
- package/src/core.ts +53 -4
- package/src/extension.ts +48 -19
package/README.md
CHANGED
|
@@ -156,7 +156,7 @@ function f() {
|
|
|
156
156
|
|
|
157
157
|
## Rendering
|
|
158
158
|
|
|
159
|
-
The UI draws the change as a unified diff with word-level highlighting (`details.diff`). The transcript card shows that diff plus a compact `+adds / -removals · N hunks` line — the per-hunk report, the anchor caveats and the shorter-form tip are written for the model and are **not** drawn next to the diff (Nik, 2026-10-01: "только сам дифф"). The model-facing content stays a summary, because models that see raw diffs in tool output start imitating the diff format in their own patches (this happened, and is why the escaping and diff-style diagnostics above exist).
|
|
159
|
+
The UI draws the change as a unified diff with word-level highlighting (`details.diff`). A diff too large for the card keeps **both ends** — the head, a dim `… (N diff lines omitted) …` marker, and the tail — and the `+adds / -removals` counts always describe the whole diff, never the excerpt (2026-10-08: a rewritten file rendered as a wall of `-` lines with `+0` in the header). The transcript card shows that diff plus a compact `+adds / -removals · N hunks` line — the per-hunk report, the anchor caveats and the shorter-form tip are written for the model and are **not** drawn next to the diff (Nik, 2026-10-01: "только сам дифф"). The model-facing content stays a summary, because models that see raw diffs in tool output start imitating the diff format in their own patches (this happened, and is why the escaping and diff-style diagnostics above exist).
|
|
160
160
|
A failed call draws its **error text** instead: a patch rejection carries the whole diagnosis there, and rendering the empty result showed "+0 / -0 · 0 hunks", hiding the reason (2026-10-07).
|
|
161
161
|
|
|
162
162
|
|
package/package.json
CHANGED
package/src/core.ts
CHANGED
|
@@ -1223,9 +1223,50 @@ export function sequentialDiffs(original: string[], resolved: ResolvedHunk[]): s
|
|
|
1223
1223
|
return diffs;
|
|
1224
1224
|
}
|
|
1225
1225
|
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1226
|
+
/** Trim a diff for the transcript, keeping BOTH ends. A whole-file rewrite is one
|
|
1227
|
+
* block of removals followed by one block of additions, so cutting the head only
|
|
1228
|
+
* showed a wall of "-" lines with nothing to see (2026-10-08: every big `write`
|
|
1229
|
+
* rendered that way). The reported counts describe the FULL diff, never the
|
|
1230
|
+
* excerpt — the card header used to read "+0 / -212" for a rewritten file. */
|
|
1231
|
+
export function truncateDiff(
|
|
1232
|
+
diff: string,
|
|
1233
|
+
max = MAX_DIFF_CHARS,
|
|
1234
|
+
): { text: string; truncated: boolean; omitted: number; additions: number; removals: number } {
|
|
1235
|
+
const lines = diff.split("\n");
|
|
1236
|
+
let additions = 0;
|
|
1237
|
+
let removals = 0;
|
|
1238
|
+
for (const line of lines) {
|
|
1239
|
+
if (line.startsWith("+++") || line.startsWith("---")) continue;
|
|
1240
|
+
if (line.startsWith("+")) additions++;
|
|
1241
|
+
else if (line.startsWith("-")) removals++;
|
|
1242
|
+
}
|
|
1243
|
+
if (diff.length <= max) return { text: diff, truncated: false, omitted: 0, additions, removals };
|
|
1244
|
+
|
|
1245
|
+
const headBudget = Math.floor(max * 0.6);
|
|
1246
|
+
const tailBudget = max - headBudget;
|
|
1247
|
+
let headEnd = 0;
|
|
1248
|
+
let used = 0;
|
|
1249
|
+
while (headEnd < lines.length) {
|
|
1250
|
+
const cost = lines[headEnd].length + 1;
|
|
1251
|
+
if (used + cost > headBudget) break;
|
|
1252
|
+
used += cost;
|
|
1253
|
+
headEnd++;
|
|
1254
|
+
}
|
|
1255
|
+
let tailStart = lines.length;
|
|
1256
|
+
used = 0;
|
|
1257
|
+
while (tailStart > headEnd) {
|
|
1258
|
+
const cost = lines[tailStart - 1].length + 1;
|
|
1259
|
+
if (used + cost > tailBudget) break;
|
|
1260
|
+
used += cost;
|
|
1261
|
+
tailStart--;
|
|
1262
|
+
}
|
|
1263
|
+
const omitted = lines.length - headEnd - (lines.length - tailStart);
|
|
1264
|
+
const text = [
|
|
1265
|
+
...lines.slice(0, headEnd),
|
|
1266
|
+
`… (${omitted} diff lines omitted) …`,
|
|
1267
|
+
...lines.slice(tailStart),
|
|
1268
|
+
].join("\n");
|
|
1269
|
+
return { text, truncated: true, omitted, additions, removals };
|
|
1229
1270
|
}
|
|
1230
1271
|
|
|
1231
1272
|
export interface EditOutcome {
|
|
@@ -1481,7 +1522,11 @@ export function unifiedDiff(oldText: string, newText: string, filePath: string,
|
|
|
1481
1522
|
// Unified-diff parsing + word-level diff (for syntax-highlighted diff rendering)
|
|
1482
1523
|
// ---------------------------------------------------------------------------
|
|
1483
1524
|
|
|
1484
|
-
|
|
1525
|
+
/** "gap" is the omission marker a truncated diff carries ("… (12 diff lines
|
|
1526
|
+
* omitted) …"). It is a line kind of its own so the renderer draws it dim
|
|
1527
|
+
* instead of dropping it: silently jumping from the head of the diff to its tail
|
|
1528
|
+
* looks like a complete (and wrong) diff. */
|
|
1529
|
+
export type DiffLineKind = " " | "-" | "+" | "gap";
|
|
1485
1530
|
|
|
1486
1531
|
export interface ParsedDiffLine {
|
|
1487
1532
|
kind: DiffLineKind;
|
|
@@ -1514,6 +1559,10 @@ export function parseUnifiedDiff(diffText: string): ParsedHunk[] {
|
|
|
1514
1559
|
}
|
|
1515
1560
|
if (current === undefined) continue;
|
|
1516
1561
|
if (line.startsWith("\\")) continue;
|
|
1562
|
+
if (line.startsWith("… (")) {
|
|
1563
|
+
current.lines.push({ kind: "gap", text: line });
|
|
1564
|
+
continue;
|
|
1565
|
+
}
|
|
1517
1566
|
const kind = line[0];
|
|
1518
1567
|
if (kind !== " " && kind !== "-" && kind !== "+") continue;
|
|
1519
1568
|
current.lines.push({ kind: kind as DiffLineKind, text: line.slice(1) });
|
package/src/extension.ts
CHANGED
|
@@ -240,14 +240,14 @@ export default function editFileExtension(pi: { registerTool: (t: unknown) => vo
|
|
|
240
240
|
const diffText = sequentialDiffs(file.lines, resolved)
|
|
241
241
|
.filter(Boolean)
|
|
242
242
|
.join("\n");
|
|
243
|
-
const { text: diff, truncated } = truncateDiff(diffText);
|
|
243
|
+
const { text: diff, truncated, additions, removals } = truncateDiff(diffText);
|
|
244
244
|
|
|
245
245
|
// No raw diff in model-facing content: models started mimicking the
|
|
246
246
|
// unified-diff format when writing patches (see pi-hermes-memory
|
|
247
247
|
// session 2026-09-25). The UI renders details.diff; the model gets
|
|
248
248
|
// the per-hunk summary only.
|
|
249
249
|
const text = `${summary}\nfile: ${params.path} — now ${updated.length} lines (was ${originalCount})`;
|
|
250
|
-
return { text, reports, reportLines, totalLines: updated.length, truncated, diff };
|
|
250
|
+
return { text, reports, reportLines, totalLines: updated.length, truncated, diff, additions, removals };
|
|
251
251
|
});
|
|
252
252
|
|
|
253
253
|
return {
|
|
@@ -259,6 +259,8 @@ export default function editFileExtension(pi: { registerTool: (t: unknown) => vo
|
|
|
259
259
|
totalLines: result.totalLines,
|
|
260
260
|
diff: result.diff,
|
|
261
261
|
diffTruncated: result.truncated,
|
|
262
|
+
diffAdditions: result.additions,
|
|
263
|
+
diffRemovals: result.removals,
|
|
262
264
|
},
|
|
263
265
|
};
|
|
264
266
|
},
|
|
@@ -290,16 +292,24 @@ export default function editFileExtension(pi: { registerTool: (t: unknown) => vo
|
|
|
290
292
|
}
|
|
291
293
|
|
|
292
294
|
const details = result?.details as
|
|
293
|
-
| {
|
|
295
|
+
| {
|
|
296
|
+
path?: string;
|
|
297
|
+
hunks?: unknown[];
|
|
298
|
+
reportLines?: string[];
|
|
299
|
+
totalLines?: number;
|
|
300
|
+
diff?: string;
|
|
301
|
+
diffTruncated?: boolean;
|
|
302
|
+
diffAdditions?: number;
|
|
303
|
+
diffRemovals?: number;
|
|
304
|
+
}
|
|
294
305
|
| undefined;
|
|
295
306
|
const diff = details?.diff ?? "";
|
|
296
307
|
const diffLines = diff ? diff.split("\n") : [];
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
}
|
|
308
|
+
// The counts describe the FULL diff and arrive with the result; counting the
|
|
309
|
+
// rendered excerpt instead reported "+0" for a rewritten file (2026-10-08).
|
|
310
|
+
const counted = countDiffLines(diffLines);
|
|
311
|
+
const additions = details?.diffAdditions ?? counted.additions;
|
|
312
|
+
const removals = details?.diffRemovals ?? counted.removals;
|
|
303
313
|
|
|
304
314
|
const hunkCount = details?.hunks?.length ?? 0;
|
|
305
315
|
let text = theme.fg("success", `+${additions}`);
|
|
@@ -339,6 +349,19 @@ export default function editFileExtension(pi: { registerTool: (t: unknown) => vo
|
|
|
339
349
|
registerWriteDiff(pi);
|
|
340
350
|
}
|
|
341
351
|
|
|
352
|
+
/** Line counts of a rendered diff excerpt; the stored full-diff stats win when
|
|
353
|
+
* present (see truncateDiff). Kept here so both renderers agree. */
|
|
354
|
+
function countDiffLines(lines: string[]): { additions: number; removals: number } {
|
|
355
|
+
let additions = 0;
|
|
356
|
+
let removals = 0;
|
|
357
|
+
for (const line of lines) {
|
|
358
|
+
if (line.startsWith("+++") || line.startsWith("---")) continue;
|
|
359
|
+
if (line.startsWith("+")) additions++;
|
|
360
|
+
else if (line.startsWith("-")) removals++;
|
|
361
|
+
}
|
|
362
|
+
return { additions, removals };
|
|
363
|
+
}
|
|
364
|
+
|
|
342
365
|
const WRITE_TOOL_NAME = "write";
|
|
343
366
|
|
|
344
367
|
/** Old text of a file worth diffing: regular, text, within the size cap. */
|
|
@@ -389,11 +412,11 @@ function registerWriteDiff(pi: { registerTool: (t: unknown) => void }) {
|
|
|
389
412
|
|
|
390
413
|
const diffText = unifiedDiff(before, params.content, params.path);
|
|
391
414
|
if (!diffText) return result;
|
|
392
|
-
const { text: diff, truncated } = truncateDiff(diffText);
|
|
415
|
+
const { text: diff, truncated, additions, removals } = truncateDiff(diffText);
|
|
393
416
|
|
|
394
417
|
return {
|
|
395
418
|
...(result as Record<string, unknown>),
|
|
396
|
-
details: { path: absPath, diff, diffTruncated: truncated },
|
|
419
|
+
details: { path: absPath, diff, diffTruncated: truncated, diffAdditions: additions, diffRemovals: removals },
|
|
397
420
|
};
|
|
398
421
|
},
|
|
399
422
|
|
|
@@ -416,7 +439,9 @@ function registerWriteDiff(pi: { registerTool: (t: unknown) => void }) {
|
|
|
416
439
|
},
|
|
417
440
|
|
|
418
441
|
renderResult(result: any, options: { expanded: boolean; isPartial: boolean }, theme: any, context: any) {
|
|
419
|
-
const details = result?.details as
|
|
442
|
+
const details = result?.details as
|
|
443
|
+
| { diff?: string; diffTruncated?: boolean; diffAdditions?: number; diffRemovals?: number }
|
|
444
|
+
| undefined;
|
|
420
445
|
const diff = details?.diff ?? "";
|
|
421
446
|
if (!diff) {
|
|
422
447
|
// New file or no-op: keep pi's own result rendering (it clears the
|
|
@@ -431,13 +456,9 @@ function registerWriteDiff(pi: { registerTool: (t: unknown) => void }) {
|
|
|
431
456
|
}
|
|
432
457
|
|
|
433
458
|
const lines = diff.split("\n");
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
if (line.startsWith("+++") || line.startsWith("---")) continue;
|
|
438
|
-
if (line.startsWith("+")) additions++;
|
|
439
|
-
if (line.startsWith("-")) removals++;
|
|
440
|
-
}
|
|
459
|
+
const counted = countDiffLines(lines);
|
|
460
|
+
const additions = details?.diffAdditions ?? counted.additions;
|
|
461
|
+
const removals = details?.diffRemovals ?? counted.removals;
|
|
441
462
|
|
|
442
463
|
let text = theme.fg("success", `+${additions}`);
|
|
443
464
|
text += theme.fg("dim", " / ");
|
|
@@ -527,6 +548,14 @@ function renderDiffBody(diffText: string, absPath: string | undefined, theme: an
|
|
|
527
548
|
let i = 0;
|
|
528
549
|
|
|
529
550
|
while (i < lines.length) {
|
|
551
|
+
if (lines[i].kind === "gap") {
|
|
552
|
+
// The marker a truncated diff carries; without this branch the tail of
|
|
553
|
+
// the diff would sit right under its head, looking like one diff (2026-10-08).
|
|
554
|
+
out.push(theme.fg("dim", lines[i].text));
|
|
555
|
+
i++;
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
|
|
530
559
|
if (lines[i].kind === " ") {
|
|
531
560
|
out.push(` ${newLine(lines[i].text, b)}`);
|
|
532
561
|
b++;
|