@bacnh85/pi-model-tools 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 CHANGED
@@ -92,6 +92,9 @@ Rules:
92
92
  - Context+removed must match **uniquely** in the file (diverges from Codex's
93
93
  first-match for safety, matching `edit`'s philosophy). Add more context lines
94
94
  if a match is ambiguous.
95
+ - If the `@@` anchor text repeats as the immediately-following context or
96
+ removed line (common when models treat `@@` as a diff-style locator header),
97
+ the duplicate is auto-collapsed — only one occurrence is matched.
95
98
  - Matching is progressive-fuzzy (exact → strip-trailing-ws → strip-both-ws →
96
99
  Unicode-normalize), so minor whitespace/Unicode differences still apply.
97
100
 
@@ -220,7 +220,7 @@ export default function (pi: ExtensionAPI) {
220
220
  name: "apply_patch",
221
221
  label: "apply_patch",
222
222
  description: [
223
- "Apply a Codex-style V4D patch to edit one or more files. Emit only changed lines plus a little surrounding context (a small diff), which is easier to get right than reproducing a large verbatim block. Supported sections: `*** Add File: <path>` (only `+` lines), `*** Delete File: <path>` (no payload), `*** Update File: <path>` or `*** Update File: <old> → <new>` (rename). Inside an Update section, each hunk is preceded by a `@@` anchor line whose text is an unchanged context line, then `-` removed lines and `+` added lines. Leading-space context lines (` `) are also allowed. Context+removed must match UNIQUELY in the file. Wrap the whole patch in `*** Begin Patch` ... `*** End Patch`.",
223
+ "Apply a Codex-style V4D patch to edit one or more files. Emit only changed lines plus a little surrounding context (a small diff), which is easier to get right than reproducing a large verbatim block. Supported sections: `*** Add File: <path>` (only `+` lines), `*** Delete File: <path>` (no payload), `*** Update File: <path>` or `*** Update File: <old> → <new>` (rename). Inside an Update section, each hunk is preceded by a `@@` anchor line whose text is an unchanged context line, then `-` removed lines and `+` added lines. Leading-space context lines (` `) are also allowed. If the @@ anchor text is restated as the immediately-following context or removed line, the duplicate is auto-collapsed. Context+removed must match UNIQUELY in the file. Wrap the whole patch in `*** Begin Patch` ... `*** End Patch`.",
224
224
  "",
225
225
  "Example:",
226
226
  "*** Begin Patch", "*** Update File: src/foo.ts", "@@ export function foo()", "- return 1", "+ return 2", "*** End Patch",
@@ -229,6 +229,7 @@ export default function (pi: ExtensionAPI) {
229
229
  promptGuidelines: [
230
230
  "Use apply_patch for multi-line or multi-file edits: emit a small diff (context + -/+ lines) instead of reproducing large verbatim oldText blocks.",
231
231
  "Each Update hunk needs a unique anchor: include enough unchanged context lines so the context+removed block matches exactly once in the file.",
232
+ "If the @@ anchor repeats on the very next line (as space-context or -removed), the duplicate is auto-collapsed.",
232
233
  "For a single tiny one-line replacement, edit is fine; for anything larger or spanning multiple files, prefer apply_patch.",
233
234
  ],
234
235
  parameters: Type.Object({ patch: Type.String({ description: "The V4D patch text, wrapped in *** Begin Patch ... *** End Patch." }) }),
@@ -21,6 +21,10 @@
21
21
  * Divergence from Codex: context+removed blocks must match UNIQUELY (Codex
22
22
  * takes the first match). Silent wrong-location edits are worse than a clear
23
23
  * error, and this matches pi's own `edit` philosophy.
24
+ *
25
+ * Leniency: if the @@ anchor text repeats as the immediately-following context
26
+ * or removed line (common when models treat `@@` as a git-diff-style locator
27
+ * header), the duplicate is collapsed automatically.
24
28
  */
25
29
 
26
30
  import { readFile, writeFile, unlink } from "node:fs/promises";
@@ -208,6 +212,17 @@ function eqRstrip(a: string, b: string) { return a.trimEnd() === b.trimEnd(); }
208
212
  function eqTrim(a: string, b: string) { return a.trim() === b.trim(); }
209
213
  function eqUnicode(a: string, b: string) { return normalizeAscii(a) === normalizeAscii(b); }
210
214
 
215
+ /**
216
+ * Loose equality matching seekSequence's 4-pass progression. Used for dedup
217
+ * so the collapse is consistent with the matcher's own fuzzy boundaries.
218
+ */
219
+ function linesLooselyEqual(a: string, b: string): boolean {
220
+ return a === b
221
+ || a.trimEnd() === b.trimEnd()
222
+ || a.trim() === b.trim()
223
+ || normalizeAscii(a) === normalizeAscii(b);
224
+ }
225
+
211
226
  /**
212
227
  * Count matches of `pattern` lines within `lines`, trying progressively
213
228
  * lenient equality. Returns count and first start index under the strictest
@@ -325,12 +340,23 @@ export async function applyPatchToFiles(parsed: ParsedPatch, cwd: string): Promi
325
340
  const newHunks: { start: number; removedLen: number; added: string[] }[] = [];
326
341
 
327
342
  for (const h of hunks) {
328
- const anchor = [...h.context, ...h.removed];
329
- const { count, firstIndex, exact: hExact } = seekSequence(work, anchor);
343
+ // ponytail: collapse a redundant @@ anchor the model restated as
344
+ // the first payload line (context or removed). Models treat `@@` like
345
+ // a git-diff locator header and repeat it as context/removed, which
346
+ // would otherwise require the line to appear twice consecutively in
347
+ // the file. Real Codex makes @@ a pure cursor-advance locator; we keep
348
+ // it in the match block so unique-match disambiguation still works
349
+ // (@@ fn / -dup), but drop the duplicate. Upgrade to full cursor
350
+ // semantics if unique-match is ever relaxed.
351
+ let matchBlock = [...h.context, ...h.removed];
352
+ if (matchBlock.length >= 2 && linesLooselyEqual(matchBlock[0], matchBlock[1])) {
353
+ matchBlock = matchBlock.slice(1);
354
+ }
355
+ const { count, firstIndex, exact: hExact } = seekSequence(work, matchBlock);
330
356
  if (!hExact) exact = false;
331
357
  if (count === 0) throw new Error(`Hunk context not found in ${op.path}. Ensure context lines match the file.`);
332
358
  if (count > 1) throw new Error(`Hunk context is ambiguous (${count} matches) in ${op.path}. Add more surrounding context lines to make it unique.`);
333
- const removedStart = firstIndex + h.context.length;
359
+ const removedStart = firstIndex + (matchBlock.length - h.removed.length);
334
360
  newHunks.push({ start: removedStart, removedLen: h.removed.length, added: h.added });
335
361
  }
336
362
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bacnh85/pi-model-tools",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Unified tool-wrapping, argument repair, reasoning management, DeepSeek V4 guidance + Super Power Mode, defensive leak-cleaning, edit mismatch repair (read-notice stripping + trim-tolerant retry), and a Codex-style apply_patch diff tool for DeepSeek V4 and GLM model families from any provider.",
5
5
  "type": "module",
6
6
  "license": "MIT",