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
~/.cursor/mcp.json
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }.vscode/mcp.json
{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }~/.codeium/windsurf/mcp_config.json
{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }MCP settings
{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.