pi-edit-file 0.2.1 → 0.2.2

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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/src/core.ts +27 -8
package/README.md CHANGED
@@ -166,7 +166,7 @@ The same renderer is attached to a `write` override, so overwriting an existing
166
166
  npm test
167
167
  ```
168
168
 
169
- 99 tests over the pure core (parser, matching ladder, atomicity, diagnostics, report caveats, diff generation, CRLF handling). The core has no Pi imports, so it runs on plain Node ≥ 22.6 with the built-in type stripping.
169
+ The suite covers the pure core (parser, matching ladder, atomicity, diagnostics, report caveats, diff generation, CRLF handling) — the count lives in the test output, not here, so it cannot go stale. The core has no Pi imports, so it runs on plain Node ≥ 22.6 with the built-in type stripping.
170
170
 
171
171
  ## License
172
172
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-edit-file",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Block-based atomic file editing for Pi agents: one flat patch string, precise match diagnostics, word-level diff rendering",
5
5
  "keywords": [
6
6
  "pi-package",
package/src/core.ts CHANGED
@@ -663,9 +663,13 @@ function indentMismatch(fileLines: string[], start: number, hunk: Hunk): string
663
663
  : "file indents with spaces, the patch's new lines use tabs";
664
664
  }
665
665
 
666
- function contextDump(lines: string[], hint: number | null): string {
667
- // Without a hint there is no centre; show the head of the file instead.
668
- const center = hint === null ? Math.min(3, lines.length) : Math.min(Math.max(hint, 1), lines.length);
666
+ /** Seven lines of the file centred on the hunk's hint. Only called with a hint:
667
+ * without one there is no place to centre on, and the head of the file is not
668
+ * context for a block that sits 200 lines further down (2026-10-08: a batch in
669
+ * executor.py dumped the same six head lines under two different failures, while
670
+ * the blocks belonged around lines 200+). */
671
+ function contextDump(lines: string[], hint: number): string {
672
+ const center = Math.min(Math.max(hint, 1), lines.length);
669
673
  const from = Math.max(0, center - 4);
670
674
  const to = Math.min(lines.length, center + 3);
671
675
  const pad = String(to).length;
@@ -767,9 +771,11 @@ function resolveOne(hunk: Hunk, fileLines: string[], index: number): { ok: Resol
767
771
  kind,
768
772
  reason: "before-block not found (exact, trim and whitespace-collapse matching all failed)",
769
773
  detail:
770
- `${diffStyleHint}${diagnoseBlock(fileLines, hunk.before, hint)}\n` +
771
- (hint === null ? `File head:\n` : `Lines around ${hint}:\n`) +
772
- `${contextDump(fileLines, hint)}` +
774
+ `${diffStyleHint}${diagnoseBlock(fileLines, hunk.before, hint)}` +
775
+ // The hint is the only thing that says where the block was meant to be.
776
+ // Without one the diagnosis above (per-line closest candidates, and the
777
+ // candidate region when there is one) is all the location there is.
778
+ (hint === null ? "" : `\nLines around ${hint}:\n${contextDump(fileLines, hint)}`) +
773
779
  (suggestion ? `\n${suggestion.text}` : ""),
774
780
  suggestion: suggestion?.patch,
775
781
  },
@@ -881,10 +887,23 @@ export function resolveHunks(hunks: Hunk[], fileLines: string[]): ResolvedHunk[]
881
887
  if (r) byIndex.set(i, r);
882
888
  }
883
889
  // Full detail for the first couple of failures; the rest stay one-line to
884
- // keep the error readable for large batches.
890
+ // keep the error readable for large batches. An identical diagnosis is printed
891
+ // once — two failing hunks must not repeat the same six lines (2026-10-08).
885
892
  const DETAIL_LIMIT = 2;
893
+ const printed = new Map<string, number>();
886
894
  report.failures.forEach((f, k) => {
887
- const detail = k < DETAIL_LIMIT ? f.detail : f.detail ? "\n(details omitted — fix the failures above first)" : "";
895
+ let detail = "";
896
+ if (k >= DETAIL_LIMIT) {
897
+ detail = f.detail ? "\n(details omitted — fix the failures above first)" : "";
898
+ } else if (f.detail) {
899
+ const seen = printed.get(f.detail);
900
+ if (seen === undefined) {
901
+ printed.set(f.detail, f.index + 1);
902
+ detail = f.detail;
903
+ } else {
904
+ detail = `\n(same diagnosis as hunk ${seen})`;
905
+ }
906
+ }
888
907
  lines.push(`hunk ${f.index + 1}: REJECTED — ${f.reason}${detail}`);
889
908
  });
890
909
  for (let i = 0; i < hunks.length; i++) {