@nicia-ai/prose-diff
v0.2.0
Published
Pure, zero-IO markdown block-diff engine: parse markdown into blocks, match block identity across edits and moves, and compute byte-faithful block-level suggestion hunks.
Maintainers
Readme
@nicia-ai/prose-diff
A pure, zero-IO markdown block-diff engine: parse markdown into an ordered list of blocks, match block identity across edits and moves, and compute byte-faithful block-level suggestion hunks.
No filesystem, network, or database access anywhere in this package — markdown strings in, plain data structures out. Extracted from Corpus's document-review engine so both Corpus and other Nicia products can share one implementation instead of drifting copies.
Installation
npm install @nicia-ai/prose-diffWhat's in here
parseBlocks/parseBlocksWithRanges(block-parse) — decomposes a markdown document into blocks (paragraph, heading, list item, code, table row, blockquote, thematic break, raw HTML) viaremark/mdast. List items and table rows are their own blocks, so a comment or suggestion can anchor to a single bullet or row. A leading YAML frontmatter fence is stripped before parsing (seefrontmatterLength).matchBlocks(block-match) — content-first structural matching of a block list against its predecessor. Identity follows what a block says, not where it sits, so a relocated block keeps its id. Exact matches carry by content signature; near-matches carry via idf-weighted token similarity (MODIFIED_SIMILARITY_THRESHOLD); everything else is an insert, and every predecessor left unmatched is reported as deleted. This is the anchor lens: built for comment/suggestion anchors that must survive edits and moves.alignBlocks(block-align) — order-preserving alignment for the suggestion diff. UnlikematchBlocks, alignment never reorders: a moved block becomes an explicit delete + insert pair, exactly whatgitshows. Patience anchoring + bounded LCS/similarity DPs under one shared cell budget keep this fast on real documents.diffSuggestion/diffToHunks/applyHunks(suggestion) — turns a base/proposed markdown pair into an ordered list of block-levelHunks (replace/insert/delete), and reconstructs the post-review document by reverting whichever hunks a reviewer rejected. Byte fidelity is the contract: rejecting every hunk reproduces the base document byte-for-byte, or the diff degrades to one whole-document hunk rather than silently losing part of a proposal.frontmatterLength(frontmatter) — the byte length of a leading----fenced YAML frontmatter block, or0when there is none. The shared boundary bothblock-parseandsuggestiontile the document against.lineDiff/diffSequences(diff) — a small line-level LCS diff for rendering a prose diff (added/removed/samelines), plus the generic sequence-diff engine it's built on (equal(i, j)compares any two indexable sequences, not just lines — the same engine also drives change-flash-style "what moved" detection).
Two-layer pipeline: diffToHunks gives block-granular hunks (a whole
paragraph/list-item/etc. replaced, inserted, or deleted); run lineDiff
on a hunk's base/proposed text (markdown.slice(hunk.baseStart,
hunk.baseEnd) vs hunk.proposedText) to get the line-level highlight
within that hunk. Consumers typically pair both rather than picking one.
See src/index.ts for the full exported surface.
Quick start
import { diffToHunks, applyHunks } from "@nicia-ai/prose-diff";
const base = "# Title\n\nOriginal paragraph.";
const proposed = "# Title\n\nEdited paragraph.";
const hunks = diffToHunks(base, proposed);
// [{ ordinal: 1, op: "replace", baseStart: ..., proposedText: "Edited paragraph.", ... }]
const withHunkRejected = applyHunks({ base, proposed, rejected: hunks });
// withHunkRejected === baseDevelopment
pnpm install
pnpm check # typecheck + lint + format:check + test + buildReleasing
Releases go through Changesets:
pnpm changeset # describe the change
pnpm version-packages # bump version + changelog from pending changesets
pnpm release # build and `changeset publish`CI publishes via npm's OIDC trusted publishing (.github/workflows/release.yml,
mirroring TypeGraph) — no stored npm token, but it requires a trusted
publisher configured for the @nicia-ai scope on npmjs.com first. Until
that's set up (or for a manual release), the exact one-shot command with
local npm publish credentials:
pnpm build && npm publish --access publicLicense
Apache-2.0 — see LICENSE.
