ProvenPin Install

jsdiff unified patches: create, apply with fuzz, reverse and word diff: working versions (tested 2026-10-15)

Create unified diffs with the diff package, apply them (with fuzz for drifted sources), reverse a patch, count added/removed lines and render word-level diffs.

passed5 passed · 0 failed2026-10-15 20:09 UTC
Status
passed
Last passed
2026-10-15 20:09 UTC
Tests
5 / 5
Language
TypeScript
Runtime
node v22.23.3
pinned versions3
diff@9.0.0@types/node@26.6.5tsx@4.23.15
run it
npm install --no-audit --no-fund --ignore-scripts=false
npm test
test environment
node v22.23.3
linux 6.17.0-1022-azure
x64
runner: github-actions 38799498972

The code

Exactly the files that passed. Your agent can pull them with get_recipe("jsdiff-patch-apply") (install), or see them on GitHub.

package.json
{
  "name": "jsdiff-patch-apply",
  "private": true,
  "type": "module",
  "scripts": {
    "test": "node --import tsx --test test/*.test.ts"
  },
  "dependencies": {
    "diff": "9.0.0"
  },
  "devDependencies": {
    "@types/node": "26.6.5",
    "tsx": "4.23.15"
  }
}
src/patch.ts
import { createTwoFilesPatch, applyPatch, parsePatch, diffWords, diffLines, structuredPatch, reversePatch } from "diff";

export const makePatch = (name: string, before: string, after: string) =>
  createTwoFilesPatch(`a/${name}`, `b/${name}`, before, after, "", "", { context: 2 });

export function apply(source: string, patch: string, fuzzFactor = 0) {
  const out = applyPatch(source, patch, { fuzzFactor });
  return out === false ? null : out;
}

export function undo(patched: string, patch: string) {
  const [p] = parsePatch(patch);
  const out = applyPatch(patched, reversePatch(p));
  return out === false ? null : out;
}

export function stats(before: string, after: string) {
  let added = 0, removed = 0;
  for (const part of diffLines(before, after)) {
    if (part.added) added += part.count ?? 0;
    if (part.removed) removed += part.count ?? 0;
  }
  return { added, removed, hunks: structuredPatch("x", "x", before, after, "", "", { context: 2 }).hunks.length };
}

/** Inline word-level diff rendered with [-old-]{+new+} markers. */
export const wordDiff = (a: string, b: string) =>
  diffWords(a, b).map((p) => (p.added ? `{+${p.value}+}` : p.removed ? `[-${p.value}-]` : p.value)).join("");
test/patch.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import { makePatch, apply, undo, stats, wordDiff } from "../src/patch.ts";

const before = ["line 1", "line 2", "line 3", "line 4", "line 5", "line 6", "line 7", "line 8", ""].join("\n");
const after = ["line 1", "line two", "line 3", "line 4", "line 5", "line 6", "line 7", "line 8", "line 9", ""].join("\n");

test("unified patch has headers and hunks", () => {
  const p = makePatch("notes.txt", before, after);
  assert.match(p, /^--- a\/notes\.txt/m);
  assert.match(p, /^\+\+\+ b\/notes\.txt/m);
  assert.match(p, /^-line 2$/m);
  assert.match(p, /^\+line two$/m);
  assert.equal((p.match(/^@@/gm) ?? []).length, 2);
});

test("patch applies and reverses", () => {
  const p = makePatch("notes.txt", before, after);
  const patched = apply(before, p);
  assert.equal(patched, after);
  assert.equal(undo(patched!, p), before);
});

test("conflicting source fails without fuzz, succeeds with fuzz", () => {
  const p = makePatch("notes.txt", before, after);
  // jsdiff rule: context lines right next to an insertion must match exactly, so drift a line further away
  const drifted = before.replace("line 4", "line FOUR");
  assert.equal(apply(drifted, p), null);
  const fuzzed = apply(drifted, p, 1);
  assert.ok(fuzzed?.includes("line two"));
  assert.ok(fuzzed?.includes("line FOUR"));
});

test("line stats", () => {
  assert.deepEqual(stats(before, after), { added: 2, removed: 1, hunks: 2 });
});

test("word diff markers", () => {
  assert.equal(wordDiff("wear safety glasses", "wear safety goggles"), "wear safety [-glasses-]{+goggles+}");
});

Connect to ProvenPin

Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.