Skip to content
Conduit

skill

jubarte-redlines

Word-faithful DOCX redlines and rendering without Word: a Rust engine with a Mac app, in-browser wasm, and Python and Node bindings. Scored against Microsoft Word.

jandira-tech/jubarte-redlinescommunitylistedunreviewed

Set up

Skills are installed by placing their folder where your agent loads skills.

The download comes from github.com/jandira-tech/jubarte-redlines itself, not from our server, so what you get is exactly what its author published.

Install instructions come from the README

Skills do not use an MCP config. Follow the steps in the README below.

README

synced Oct 25, 2026

See every page side by side: jandira-tech.github.io/neurotic_docx_bench
jubarte vs Microsoft Word, docxide-pdf, LibreOffice, PyMuPDF Pro, MiniPdf, rdocx and office2pdf on 808 documents, DOCX to PDF, scored per page.

jubarte

Word-faithful DOCX redlines and rendering, without Word.

Compare two Word documents into native tracked changes, inspect or resolve changes programmatically, apply validated edit plans, and render DOCX to PDF or PNG — from Rust, Python, Node/browser, or the CLI.

OpenSSF Best Practices CI codecov crates.io docs.rs PyPI npm MSRV license Socket Badge

Jubarte is an in-process DOCX engine for applications that need Microsoft Word-style review workflows without automating Microsoft Word or LibreOffice.

  • Compare DOCX → tracked DOCX with native insertions, deletions, moves and formatting changes.
  • List, accept or reject changes globally or selectively by change ID, author and kind.
  • Render DOCX → PDF / PNG with an independent Word-oriented layout engine.
  • Inspect and edit documents safely using paragraph IDs, source hashes and atomic JSON edit plans.
  • Use the same core engine everywhere: Rust, CLI, Python and WebAssembly for Node/browser.
  • Keep the original package: relationships, styles, headers/footers, notes, media and other DOCX parts are preserved by the comparison workflow.

Jubarte is not affiliated with Microsoft. “Word-faithful” describes engineering targets measured against Microsoft Word behavior; it is not a claim that every DOCX will render byte-for-byte or page-for-page identically.

Live benchmark, every page against Microsoft Word: jandira-tech.github.io/neurotic_docx_bench · tables: neurotic_docx_bench RESULTS.md

Feedback and contributions

Report bugs and request enhancements in the public issue tracker, which also archives reports and responses. English reports are welcome. See CONTRIBUTING.md for pull requests, coding requirements, building and tests, and SECURITY.md for private vulnerability reports. The OpenSSF evidence review tracks the criteria and outstanding maintainer attestations.

Quick start

Install the CLI:

cargo install jubarte-redlines
jubarte --version

Prebuilt archives are also published on GitHub Releases. Check the assets attached to the release you intend to install.

Compare two Word documents

jubarte original.docx modified.docx \
  -o redline.docx \
  --author "Reviewer"

redline.docx contains native Word tracked changes.

Without -o, jubarte prints the redline as the agent view and writes nothing; -o redline.pdf renders it instead.

Inspect and resolve tracked changes

jubarte changes redline.docx          # one change per line (a replacement is one): r12, kind, target, author, paragraph, text

# Resolve one specific change and leave the others tracked:
jubarte accept redline.docx \
  -o partially-accepted.docx \
  --id r12

# Or resolve everything:
jubarte accept redline.docx -o final.docx
jubarte reject redline.docx -o original-state.docx

Selection flags can be repeated and combined:

jubarte reject redline.docx \
  -o reviewed.docx \
  --author "Reviewer A" \
  --kind formatting

Render DOCX to PDF or PNG

# PDF
jubarte convert redline.docx \
  -o redline.pdf \
  --revisions word \
  --compress

# Page images: redline-page-01.png, redline-page-02.png, ...
jubarte convert redline.docx \
  -o redline.png \
  --dpi 120

No Microsoft Word or LibreOffice process is launched.

How the workflow fits together

flowchart LR
    A[original.docx] --> C[Compare]
    B[modified.docx] --> C
    C --> D[redline.docx]
    D --> E[List changes]
    E --> F[Accept / Reject]
    F --> G[resolved.docx]

    H[DOCX] --> I[Read]
    I --> J[JSON edit plan]
    J --> K[Edit]
    K --> L[clean.docx]
    K --> M[redline.docx]

    D --> N[Render]
    G --> N
    L --> N
    M --> N
    N --> O[PDF / PNG]

Documentation

Per-surface guides, each ending in a generated reference (CLI help from the real runner, API from the shipped module) that CI keeps drift-free — scripts/gen_docs.sh regenerates them:

  • Rust guide — crate features, API tour, rendering styles, jubarte CLI reference
  • Python guide — wheel and uvx, the three library tiers, generated API reference
  • Node and browser guide — jubarte-wasm and npx jubarte-redlines, entry points, generated API reference

Topical documents: Markdown pipeline · Word differences · Word layout rules · comment balloons · self-update · benchmark classes · per-release API snapshots.

Install

CLI

cargo install jubarte-redlines

The installed binary is named jubarte.

From 0.11.0, the Python wheel and the npm package also run the CLI without an install:

uvx jubarte-redlines redline a.docx b.docx -o redline.docx
npx jubarte-redlines redline a.docx b.docx -o redline.docx

Both runners speak the shared command set — compare/redline, diff, changes, accept, reject, read, edit, add, convert, capabilities — and share the Rust clap parser for help, defaults, aliases and usage errors. The Python wheel also runs comments, validate and visual-diff; append, scrub, debug and self-update remain in the binary. The npm CLI renders no PNG pages (jubarte-wasm/cli/README.md).

For a GitHub-style text review of two Word documents:

jubarte diff a.docx b.docx --format github
jubarte diff a.docx b.docx --format github --context 5 -o changes.patch
uvx jubarte-redlines diff a.docx b.docx --format github
npx jubarte-redlines diff a.docx b.docx --format github

github (also unified or text) prints a standard unified patch with diff --git, file headers, hunk ranges and context. It compares the complete debug -c text view, including existing revision marks and story text in headers, footers, notes, comments, tables and text boxes. It extracts all content before display clipping and never caps the number of changes, and does not create an implicit DOCX. Header/footer part renumbering is paired by section role. This is a patch of extracted text, for review; it cannot patch a binary DOCX. Formatting and media differences belong to compare, debug or visual-diff.

CriticMarkup keeps its existing role: representing a document's current content with its tracked marks and comments. github gives a separate comparison of those contents; it does not accept, reject or reinterpret the marks unless --accept-changes is requested. --format word always accepts ALL changes in both inputs first, then produces fresh word-level CriticMarkup. normal, context and side-by-side are also available. Long lines show a 70-character window around the first change; --full-lines shows everything. See text diff details.

Markdown sits beside Word: jubarte diff prints the changes between any two documents, Word or Markdown, as a git-style patch; jubarte convert draft.md writes CommonMark (with CriticMarkup as tracked changes) to DOCX/PDF/PNG; and jubarte edit writes the redline's patch as patch.diff. See docs/MARKDOWN.md.

For a source checkout, the repository also contains installation scripts that can install supplemental fonts used by the renderer:

scripts/install.sh
scripts/install.sh --fonts-only

On Windows, see scripts/install.ps1.

Rust library

cargo add jubarte-redlines --no-default-features

The package is called jubarte-redlines; the Rust library import path is jubarte.

use jubarte::{convert, document_comparer};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let original = std::fs::read("original.docx")?;
    let modified = std::fs::read("modified.docx")?;

    let redline =
        document_comparer::compare_documents(&original, &modified, "Reviewer")?;
    std::fs::write("redline.docx", &redline)?;

    let pdf = convert::docx_to_pdf(&redline)?;
    std::fs::write("redline.pdf", &pdf)?;

    Ok(())
}

The default crate features include the CLI, fast allocator and self-update support. Library-only consumers can disable defaults and opt into features deliberately.

MSRV: Rust 1.94.

Python

Python requires CPython 3.10 or later.

pip install jubarte-redlines

Low-level byte API:

from pathlib import Path
from jubarte_redlines import (
    compare_documents,
    get_revisions,
    docx_to_pdf,
)

original = Path("original.docx").read_bytes()
modified = Path("modified.docx").read_bytes()

redline = compare_documents(
    original,
    modified,
    author="Reviewer",
)
Path("redline.docx").write_bytes(redline)

for revision in get_revisions(redline):
    print(revision)

Path("redline.pdf").write_bytes(
    docx_to_pdf(redline, revisions="word")
)

The richer Document API additionally supports inspection, Markdown projection, selective change resolution, edit plans, PDF/PNG rendering and preview.

Node and browser

Node 18+:

npm install jubarte-wasm
const {
  compareDocuments,
  docxToPdf,
  listChanges,
  acceptChanges,
  rejectChanges,
} = require("jubarte-wasm");

const redline = compareDocuments(originalBytes, modifiedBytes, "Reviewer");
const pdf = docxToPdf(redline, false, "word");

For applications that do not need the PDF renderer, use the slim package exports:

const {
  compareDocuments
} = require("jubarte-wasm/slim");

Browser builds are exported from jubarte-wasm/web and jubarte-wasm/web-slim.

MCP server

jubarte-mcp exposes the engine as MCP tools (read, edit as tracked changes, render, compare, accept and reject) to coding agents. Every path stays under --root.

HostInstall
Claude Codeclaude mcp add --transport stdio jubarte --scope project -- uvx --from 'jubarte-redlines[mcp]' jubarte-mcp --root .
Codexcodex mcp add jubarte -- uvx --from 'jubarte-redlines[mcp]' jubarte-mcp --root .
Gemini CLIgemini extensions install https://github.com/jandira-tech/jubarte-redlines

Tools, security model and config files: docs/adoption/mcp.md.

CLI reference

Every command, from jubarte --help (regenerated by scripts/gen_docs.sh). The complete per-command --help of every runner (the jubarte binary, the Python wheel, the npm CLI) is in the per-surface guides.

CommandWhat it does
jubarte ORIGINAL MODIFIEDRead, edit, compare and render Word documents
jubarte compareCompare documents: print the redline as the agent view, or write it with -o [alias: redline]
jubarte changesList tracked changes with IDs for accept, reject and edit plans
jubarte acceptAccept all tracked changes, or select by ID, author or kind
jubarte rejectReject all tracked changes, or select by ID, author or kind
jubarte convertConvert Word or Markdown to DOCX, PDF, PNG or Markdown
jubarte diffReview differences as GitHub, word, normal, context or side-by-side text
jubarte readRead the agent view: YAML header, <!-- pN --> id lines, changes and comments with ids
jubarte editEdit a document: -p WHERE with --anchor, --content, --delete, --resolve, --reply, --accept, --reject, --strike, --restore or --style (several -p per command), or a JSON plan. Writes FILE_modified.docx (--out-dir: the bundle), then prints SUCCESS and a fenced before/after diff of the full agent views (refusal: exit 3)
jubarte addAdd a paragraph, a comment or a reply: -p WHERE --content TEXT (several -p per command). Writes FILE_modified.docx, as edit does, and prints SUCCESS and a fenced before/after diff of the full agent views
jubarte capabilitiesWhat this binary can do, for agents choosing an operation
jubarte visual-diffCompare rendered pages pixel by pixel (different pages: exit 5)
jubarte commentsList comments, threads and the text they annotate
jubarte appendJoin documents in order, preserving images, styles, lists and notes
jubarte validateCheck Word validity, tracked edits, accessibility, style and structure; or repair Word validity
jubarte checkCheck a document against a source-bound expectation contract
jubarte scrubRemove authors, editing IDs, metadata and comments before sharing
jubarte self-updateCheck or install a release from GitHub

Compare

jubarte ORIGINAL MODIFIED [OPTIONS]

Without -o, jubarte A B (or jubarte compare A B) prints the redline as the agent view (the read options apply) and writes nothing. With -o, the extension picks what is written:

-oWrites
.docx (or no or an unknown extension)the redline
.md, .markdown, .txtthe agent view of the redline; CriticMarkup when both inputs are Markdown
.pdfthe redline, rendered (--revisions, --pages, --changed-pages apply)
.pngone STEM-page-NN.png per page
.jsonthe snapshot of the redline, as read --json prints it

jubarte FILE alone is jubarte read FILE; a lone name without an extension is taken for a mistyped task, so read such a file with jubarte read FILE.

Common options:

OptionPurpose
-b, --original FILEOriginal document
-m, --modified FILEModified document
-o, --output FILEWrite the redline; the extension picks the format (above)
-a, --author NAMERevision author (default: Redline)
-d, --datetime ISO8601Revision timestamp (default: now, UTC)
--detail-threshold RATIOComparison-detail tuning
`--mode wordpowertools`
--forceReplace an existing output
--reference-doc FILE, --critic, --resource-path DIRMarkdown inputs
--revisions, --dpi, --pages, --changed-pagesRendering, for -o FILE.pdf or .png
-q, --quietNo status lines on success; a reading task still prints what it read

The command line dates the revisions now; pin -d 1970-01-01T00:00:00Z for byte-identical output from identical inputs. The library calls keep the epoch as their default, so they stay deterministic.

List tracked changes

jubarte changes FILE.docx
jubarte changes FILE.docx --json

changes is intended for individually addressable review operations and reports stable IDs while the corresponding tracked change remains present. The text listing prints short ids (r12, header1.r1, footnotes.r3); --json prints JSON Lines with the same short id (and ref). --id takes the short ids: r12, r1+2 (both marks of one change), header1.r1. A change that holds a comment's reference mark ends its line with holds comment c4's mark (holds comment marks c4, c5 for a thread's reply too), and --json lists them in holds_comments: resolving the change so its content goes (accepting a deletion, rejecting an insertion) removes those threads, as Word does. The read view says so on the note: {>>c4 @AC in r13: …<<} when the note prints outside r13.

With human-readable CLI output, every command that writes a DOCX from a document prints a confirmation after publication (unless -q):

SUCCESS:
~~~~diff
--- before in.docx
+++ after out.docx
@@ -1,3 +1,3 @@
 ---
-source: in.docx
+source: out.docx
 sha256: …
~~~~

The diff compares the full input and output agent views, including source: and sha256:. It uses three context lines; the output view comes from the published bytes. Markdown conversion uses the original Markdown text as its before-view; comparison uses its first input. Identical views print no differences inside the fence. The fence grows past any leading tilde run in the diff. Edit's former changed-range view and resolved-lines output are replaced by this block; an existing bundle summary stays before it, with one block per DOCX. The wrote … lines and notes remain on stderr. -q silences the block, summary and status; warnings and errors still print. Dry runs, saved plans and non-DOCX outputs retain their existing output. Template generation writes a JSON plan; applying it writes the DOCX. Typed --json/--jsonl delivery retains its machine-readable receipt.

Accept or reject

jubarte accept FILE.docx -o OUTPUT.docx
jubarte reject FILE.docx -o OUTPUT.docx

Optional selection filters:

--id ID          r12, r1+2, header1.r1
--author NAME
--kind insertion|deletion|move|formatting

Filters are repeatable. Categories combine, so a change must satisfy each category you supplied. jubarte edit FILE -p r12 --accept (or --reject) resolves one change and writes FILE_modified.docx.

Add --keep-nested-comment to accept or reject to preserve a comment and its replies when settlement removes their whole anchor. Their markers move together to an empty, untracked anchor where the removed text was; the command reports, for example, kept c0 and its reply c1 on an empty anchor in p1. Partial overlaps keep the existing behavior. The default removes wholly removed threads, as Word does.

Convert to PDF / PNG

jubarte convert FILE.docx [OPTIONS]
OptionPurpose
-o, --output FILEOutput path; the extension picks the format: .docx, .md, .pdf, .png (pages named STEM-page-NN.png) or .json (the snapshot)
--dpi DPIPNG resolution; default 96
--compressDeflate PDF content streams
`--revisions conventionalword
--keep-nested-commentWith --track-changes accept or reject, keep comment threads whose whole anchor is removed, on an empty anchor; off by default
--revision-palette SPECCustom change colors/lines
--font-report FILEWrite font-resolution JSON
--report FILEWrite page/render report
--forceReplace existing output
--fail-on-substitutionExit 4 when a requested font was substituted
--pages SPECOnly these pages, for example 1-3,7, in the PDF and the PNGs alike; PNGs keep the document's page numbers
--move-commentsList comments after the last page; the text keeps its tint and a [JR1] marker
--changed-pagesKeep only the pages a change touches: a body or note revision, a comment anchor, or the first page a revised header or footer paints on (page numbers stay the document's)
-f, --from / -t, --todocx, md, pdf, png or json, when -o has no extension: Markdown in (CriticMarkup becomes tracked changes) or out (docs/MARKDOWN.md)

Without -o, a .docx becomes STEM.pdf beside it and Markdown becomes STEM.docx; -t md prints to stdout.

Markdown to Word: a change or comment that names no author or date (by its tag or the authors: table) takes -a, --author and -d, --datetime, else the front matter's author: and last_modified: (else modified:, date:, created:), else the --reference-doc's dc:creator and dcterms:created, else Redline and now. Pin -d 1970-01-01T00:00:00Z for byte-identical output; the library calls keep Redline and that epoch as their default and read no reference document's metadata. compare, edit, add and diff never take the files' metadata: their changes carry --author and --datetime (default Redline and now).

Examples:

jubarte convert contract.docx
jubarte convert redline.docx --revisions word
jubarte convert redline.docx -o pages.png --dpi 144
jubarte convert long.docx -o excerpt.pdf --pages 1-3,7
jubarte convert redline.docx -o changed.pdf --changed-pages
jubarte convert contract.docx --compress --font-report fonts.json

A document with comments gets Word's markup pane: each comment in a balloon beside its range, joined to it by a dotted connector, with the range tinted and bracketed. As Word does, no style writes a PDF comment annotation: a viewer would paint it as a note icon over the text (docs/WORD_DIFFERENCES.md).

A custom palette can be supplied with:

jubarte convert redline.docx \
  --revisions custom \
  --revision-palette \
  "deleted=#AA0000:strike,inserted=#0055FF:double-underline"

Read a document

jubarte read contract.docx               # the agent view
jubarte read contract.docx --json        # the snapshot: paragraphs, runs, tables, changes, comments
jubarte read contract.docx --tables      # top-level tN grids, with nested cell paths
jubarte read contract.docx --tables -p t0 # t0 and its nested tables: grid, then view
jubarte read contract.docx -p p12,p40-p48 -o excerpt.md

read prints the agent view: a YAML header (sha256: of the file, the revision and comment counts, authors and their handles, page setup, styles, headers, footers), then Markdown with an id line before every paragraph (p12, the body's paragraph 12) and an id on every tracked change and comment (r3+4 is the two marks with w:id 3 and 4, c5 is comment 5, header1.r1 a change in a header). A table cell's paragraph is t0.row1.cell2, a footnote's footnotes.p0, and c5.text the text comment 5 covers. No id needs quoting in bash, zsh or csh.

--json prints the snapshot instead: document metadata, story and paragraph coordinates, source_sha256, runs, structural limitations, tables, content controls, and the changes and comments with their short ref. The selection flags (-p, --head, --tail, --changed, --by) narrow it as they narrow the view. -o FILE.md or -o FILE.json writes either to a file.

The snapshot uses schema_version: 3. changes lists logical changes: a change Word wrote as several marks is one entry with its members, and a deletion followed by its author's insertion is one replacement. summary.tables and tables[] count top-level body tables only; each index is the view's tN. Every table has a nested array (empty for a leaf), containing its cell tables. A nested entry has parent (for example t0.row2.cell0) and no index. A deeper parent includes the cell-local table ordinal, for example t0.row2.cell0.t0.row0.cell1. Paragraph ids and edit-plan schemas are unchanged.

--tables numbers only top-level tables as table N, matching tN in the view and -p tN. Nested tables follow their containing cell row, indented two spaces per nesting level and labelled nested in CELL_PATH. For example, nested in t0.row2.cell0; a further level uses t0.row2.cell0.t0.row0.cell1. --tables -p t0 includes this whole tree.

<!-- p0 center -->
# Consulting Agreement

<!-- p1 justify -->
This Consulting Agreement (the “Agreement”) is made between Harbor Point Analytics LLC ("Consultant") and Juniper Freight Co. ("Client").

<!-- p2 -->
## 1. Services

<!-- p3 -->
Consultant will deliver the services in each Statement of Work, including {++quarterly++}{r0 @AC} route-cost reports and a {~~weekly~>monthly~~}{r1+2 @AC} review call.

Attribution uses braces after the change closer: {++new++}{r1 @AL}. {>>c5 @AC: note<<} remains a comment. A deletion inside another author's insertion names its parent with {r3 @BO re r1 @AL}. Distinct revision ids stay separate; only a deletion directly followed in the rendered stream by a live insertion from the same author forms {~~old~>new~~}{r4+5 @AL}. Different inline dates keep the two marks separate. An inserted paragraph mark is ¶{r7 @AL}. To write literal attribution-shaped text after a closer, escape the opening brace: {++new++}\{r1 @AL}; write literal ¶\{ likewise.

See docs/MARKDOWN.md for the grammar.

Validate

jubarte validate contract.docx
jubarte validate contract.docx --json
jubarte validate contract.docx --repair fixed.docx
jubarte validate review/redline.docx --original contract.docx --author Claude

validate lists what makes Word refuse or repair a file, beyond what an XSD sees: one line per finding (code, part#path, message, a * when it is Word-fatal), or one JSON object each with --json. --repair writes a copy with every repairable finding fixed and lists what remains; --original with --author audits that every text change against the original is a revision by that author (UNTRACKED_EDIT, FOREIGN_AUTHOR). The edits rules also report permission endpoints lost from any part (PERMISSION_RANGE_DROPPED); losing an editable region is not Word-invalid. --rules a11y,style,structure adds accessibility, style and structure findings (heading skips, images without alt text, tables without a header row, literal bullets, stale caches), each located by paragraph id; --rules also takes single rule codes.

NESTED_SAME_REVISION is non-fatal: direct same-kind nesting is info (Word opens and reads it faithfully); alternating nesting such as ins > del > ins is a warning because Word opens it but misreads reject-all (#1134, Word probes 2026-10-17).

NESTED_TEXT_BOX is a Word-fatal Ring-1 error: a DrawingML or VML text box inside another box's readable content prevents Word from opening the file (#1132, Word probes 2026-10-18). The check covers every story part and ignores nesting only in mc:Fallback; --repair leaves it unchanged.

Any word or edits finding exits 2, and so does any error; --strict fails on warnings too. A file that cannot be read exits 1.

OptionPurpose
--jsonJSON Lines: one object per finding, nothing when there is none
--rules SETSword (the default), edits, a11y, style, structure, or rule codes, comma-separated
--strictFail on warnings too
--repair FILEWrite the repaired package; remaining findings still exit 2
--original FILEAudit tracked edits against this original (needs --author)
--author NAMEThe author every change must carry
--forceReplace an existing --repair output

jubarte audit FILE still runs, as validate --rules a11y,style,structure with a note; it is removed in 0.14.

Scrub

jubarte scrub contract.docx -o outgoing.docx
jubarte scrub contract.docx -o outgoing.docx --rsids --docprops

Removes who touched a document before it goes out: author names (as one alias, --author-alias NAME), rsids, the people and dates in the document properties, and comments. Text and tracked changes stay. Without a flag all four go, under the alias "Author"; with flags, only those given.

Append documents

jubarte append a.docx b.docx c.docx -o combined.docx
jubarte append a.docx b.docx -o combined.docx --section-break continuous --keep-sections

B after A, then C after that, carrying images, links, styles, lists and notes. Comments are dropped with a warning unless --carry-comments. --section-break is next-page (the default), continuous or none; --keep-sections keeps each document's page setup, headers and footers.

Comments

jubarte comments contract.docx
jubarte comments contract.docx --json --author "Reviewer" --latest

Every comment with its thread (parent, done) and the text it is anchored to. Edit plans reply to, resolve, edit and delete comments (see the operation table below).

Fields

jubarte convert contract.docx -o updated.docx --update-fields --report fields.json

--update-fields refreshes the cached results of PAGEREF, REF, NUMPAGES, SEQ and TOC fields from jubarte's layout; TOCs are rebuilt from the headings. Field codes stay, so Word can update them again. Page numbers are jubarte's layout, not Word's (docs/WORD_DIFFERENCES.md). It runs after --track-changes accept|reject; --report lists the fields written with their old and new results.

Compare rendered pages

jubarte visual-diff before.docx after.docx
jubarte visual-diff before.docx after.docx --out-dir pages --dpi 150 --json

Lays out and rasterizes both documents at one resolution and compares them pixel for pixel. Exit 0 when every page is the same, 5 when any page differs; --out-dir writes the changed pages, overlays and diff.json.

Apply an edit plan

First read the source:

jubarte read contract.docx -o contract.json

Create plan.json:

{
  "schema_version": 1,
  "source_sha256": "<the sha256: line of jubarte read>",
  "author": "Reviewer",
  "operations": [
    {
      "id": "term",
      "kind": "replace",
      "paragraph": "p12",
      "find": "two years",
      "replacement": "three years"
    }
  ]
}

Or record it from flags, one command at a time. Nothing is edited until the plan runs:

jubarte edit contract.docx --plan plan.json -p p12 --anchor "two years" --content "three years"
jubarte add contract.docx --plan plan.json -p p14 --comment --content "Why three?"

Each recording is checked against contract.docx and pins its source_sha256; one that does not apply leaves plan.json as it was. --consume deletes the plan once it has been applied.

Preview it:

jubarte edit contract.docx \
  --plan plan.json \
  --out-dir review \
  --dry-run

Apply and render:

jubarte edit contract.docx \
  --plan plan.json \
  --out-dir review \
  --pdf \
  --png \
  --dpi 100

A successful edit can produce:

review/
  clean.docx
  redline.docx
  patch.diff
  report.jsonl
  clean.pdf
  redline.pdf
  clean-page-01.png
  redline-page-01.png
  ...

Operation kinds (jubarte capabilities lists the ones the installed build supports):

KindWhat it does
replaceReplace find with replacement in one paragraph; only the words that differ are written, so kept words keep their formatting and characters (a curly quote or a non-breaking space stays where the replacement has the plain one); optional format for the new text, "whole": true for one deletion then one insertion as typed
insertInsert text after or before a match, or at position start/end
deleteDelete a match
rewriteGive a paragraph's whole new text; only the words that differ are edited
format_runRestyle existing text as a tracked formatting change
commentComment on a match, a whole paragraph, or every paragraph up to through
reply_comment, resolve_comment, edit_comment, delete_commentWork on a comment thread by comment_id; edit_comment on another author's comment needs "rewrite_others_comments": true (it keeps their name)
strikeReject another author's insertion revision visibly: your tracked deletion inside it, with an optional comment on the struck words
restoreReject another author's deletion revision visibly: your tracked copy of its words right after it, with an optional comment
insert_paragraphA new paragraph after the anchor, with its properties (or those like selects)
delete_paragraphDelete a paragraph, optionally commenting on the deleted text
format_paragraphStyle, alignment, line spacing, space before and after, as a tracked property change
merge_paragraphsJoin the next paragraph onto this one
insert_tableA table before or after a body paragraph, tracked as inserted rows
listBulleted, numbered or lettered list, tracked as property changes
insert_footnoteA footnote after an anchor
insert_imageA picture paragraph (PNG, JPEG, GIF, BMP or TIFF)
insert_tocA table of contents field; with "update_fields": true it is filled from the headings
page_setupPage size, orientation and margins
watermarkWord's diagonal text watermark in every default header (untracked)
fill_controlFill a content control (text, choice, checkbox or date) by id, tag or alias
redactReplace text with █ blocks in both copies, untracked; refused if the text still occurs anywhere. No report, refusal or confirmation repeats the text or the document's copy of it: a plan with a redaction confirms with the published view, not the before/after diff
settingstrackRevisions, updateFields and document protection in word/settings.xml

Plans are atomic: stale sources, ambiguous anchors, overlapping edits or unsupported structures refuse the plan instead of making a guessed edit.

See skills/jubarte-documents/SKILL.md and examples/agents/acme-letter for the full plan schema and a complete example.

Capabilities

jubarte capabilities          # one line per capability
jubarte capabilities --json   # the manifest: formats, ids, validate rules, operation kinds

Use this when an application or agent needs to discover the exact surface supported by the installed build.

Self-update

jubarte self-update --check
jubarte self-update

Optional:

-y, --yes
--version VERSION

See docs/SELF_UPDATE.md.

Debugging DOCX behavior

jubarte debug FILE.docx --list
jubarte debug FILE.docx --check render
jubarte debug FILE.docx -c text
jubarte debug FILE.docx -c runs
jubarte debug FILE.docx -c xml

jubarte debug diff A.docx B.docx
jubarte debug diff A.docx B.docx C.docx --full

debug is hidden from jubarte --help: it is a diagnostic tool for engine work, not part of the stable surface. Debug catalogs cover package structure, fields, bookmarks, IDs, styles, text boxes, revision changes, numbering, normalized XML/text/runs and render information.

Use jubarte <command> --help for the authoritative option list for the installed release.

Rendering and fonts

The PDF engine reconstructs Word-oriented layout directly; it does not call Microsoft Word or LibreOffice.

The documented rendering surface includes:

  • tracked changes, and comments as Word's balloons in the markup pane (a resolved comment faded, a reply numbered under its thread);
  • paragraph/run formatting and advanced line layout;
  • CJK and RTL/complex-script text;
  • installed and embedded fonts;
  • merged, vertically merged, autofit and floating tables;
  • DrawingML/VML graphics, text boxes, charts and SmartArt;
  • raster and EMF/WMF images;
  • headers, footers and floating frames;
  • sections, columns, backgrounds and page borders;
  • footnotes and selected fields.

Rendering fidelity still depends on fonts and on document features. WebAssembly has no installed fonts: docxToPdf paints with the fonts a document embeds and the bundled Carlito and Liberation faces, so Chinese, Japanese or Korean text without an embedded font is not painted (see KNOWN_ISSUES.md, item 9).

Supplemental font lookup can be configured with:

export JUBARTE_FONT_DIR=/path/to/jubarte/fonts
export JUBARTE_FONT_INDEX=/path/to/font-index.tsv

Set:

export JUBARTE_FONT_INDEX=off

to disable the persistent font index.

Default per-user font directories are documented in the installation section of this repository.

For known Word-layout differences, see:

Comparison modes

The default comparison mode is:

--mode word

It targets Word-like comparison behavior.

Classic PowerTools behavior is available with:

--mode powertools

Choose explicitly if exact comparison semantics matter to a regression test or downstream workflow.

Existing revisions in edit plans

An edit plan can refuse documents that already contain tracked changes, or resolve them according to an explicit policy.

List changes before editing:

jubarte changes source.docx --json

Selective resolution can be part of the plan, for example:

{
  "resolve_revisions": {
    "accept": {
      "ids": ["r12"]
    },
    "reject": {
      "authors": ["Previous Reviewer"]
    }
  }
}

Conflicting selections are rejected instead of silently choosing a side.

Settling a revision that removes a comment's whole anchor removes the thread and its replies, as Word does. Edit receipts report each removed thread as removed_comments (using source ids c0, c1, etc.), each saying why: anchor removed by accepting r13, the change that held its mark. Legacy edit --dry-run prints the JSONL preview; edit --out-dir writes the summary event in report.jsonl. Typed edit apply --json includes the same records in data. To preserve the thread, set "removed_anchor_comments": "keep" in a v1 or v2 plan. Its range markers and reference runs move together to an empty, untracked anchor where the removed text was. A reply in the same plan can then use that anchor. The receipt reports kept_comments with anchor: "empty". Comments the plan subsequently deletes are not listed; deleted replies leave the kept thread's replies, while its surviving root stays listed. This opt-in is a jubarte convention that Word opens with threaded balloons; the default "remove" follows Word's removal behavior. Partial overlaps keep the existing behavior. Unknown values are refused as INVALID_PLAN. The policy is available in plans; accept/reject keep their existing warnings. A reply to a comment whose paragraph the same plan deletes goes on the deleted text beside its parent, where Word shows the thread; a v2 plan with "track": "off" deletes the anchor outright, and is refused with COMMENT_ANCHOR_REMOVED. Comment and bookmark ids stay stable while a plan runs, so later operations still address the source comments. After all operations finish, the preserving path numbers the revision-free working document once; its clean copy, tracked replay and operation receipts share that allocation. Pending source revisions retain their ids. The comparer path retains ids in both outputs: it unions comments from its original and edited inputs, so numbering only the edited input would change comment matching. Settlement receipts retain source comment ids; newly added comments report their written ids.

If settlement removes a kept thread's row, cell or whole table, its empty anchor moves to the end of the last surviving paragraph before that container in the same story, or the start of the first paragraph after it. Removed and kept receipts never list the same comment. Removal text names the acceptance or rejection that removed the thread; kept text names its published paragraph.

The v1 plan envelope is documented in schemas/edit-plan-v1.schema.json, and the typed v2 schema in schemas/edit-plan-v2.schema.json.

Examples

CLI

jubarte original.docx revised.docx \
  -o redline.docx \
  --author "Legal"

jubarte changes redline.docx --json

jubarte convert redline.docx \
  -o redline.pdf \
  --revisions word

Python Document

import jubarte_redlines as jubarte

doc = jubarte.read("contract.docx")

print(doc.markdown())
snapshot = doc.inspect()
print(snapshot)

pages = doc.to_png(dpi=120)

Agent-style document editing

See examples/agents/acme-letter, which includes an edit plan, generated report and rendered redline page.

Safety and validation

CI currently includes:

  • cargo fmt --check
  • Clippy with -D warnings on the crate and on the Python, WASM (wasm32-unknown-unknown) and in-process bench bindings
  • all-feature Rust tests on Linux, macOS and Windows
  • source-based code coverage with a line-coverage floor
  • MSRV testing on Rust 1.94 (the all-feature test suite, on Linux)
  • cargo publish --dry-run
  • cargo-deny
  • REUSE/SPDX checks
  • Python binding tests

The project also maintains package/schema validation and real-Microsoft-Word opening probes for release work because an OOXML document can be schema-valid and still trigger Word repair behavior.

See:

Benchmarks

Jubarte maintains Word-oracle benchmarks for both document comparison and DOCX rendering.

The benchmark methodology, corpus provenance, current numbers and historical results belong in:

Treat these as reproducible project-maintained measurements rather than as a substitute for testing your own document corpus.

For important production workloads, benchmark representative documents from your own environment before choosing an engine.

Supported environments

SurfaceSupported/tested target
Rust / CLICI tests Linux, macOS and Windows
Rust toolchainRust 1.94+
PythonCPython 3.10+
Python release wheelsmacOS x86_64/arm64, manylinux_2_28 x86_64/arm64, musllinux_1_2 x86_64/arm64, Windows x86_64 (abi3, CPython 3.10+)
NodeNode 18+
BrowserWebAssembly builds
CLI release workflowLinux x86_64/arm64, macOS x86_64/arm64, Windows x86_64 targets

Release assets can vary by tag. Check the release page before scripting a binary download.

A Word 97-2003 .doc converts to .docx with the native binary's jubarte convert old.doc (or jubarte::legacy_doc::doc_to_docx in Rust): text, headings, lists, bold, italic and tables. Every other command, and the Python and JavaScript packages (the npm command line too), refuse a .doc (or an encrypted document of any Word version) with LEGACY_DOC and the hint to save it as .docx without a password, and RTF with UNSUPPORTED_PACKAGE. Convert them to .docx first.

Troubleshooting

ProblemWhat to check
PDF layout differs from WordVerify required fonts; inspect --font-report; read docs/WORD_DIFFERENCES.md
Text is missing or wraps differentlyCheck font resolution and embedded/system font availability
Edit exits with a refusalRead report.jsonl / stdout for STALE_SOURCE, ANCHOR_NOT_FOUND, AMBIGUOUS_ANCHOR, OVERLAPPING_EDITS, UNSUPPORTED_STRUCTURE, etc.
STALE_SOURCERe-run jubarte read and regenerate the plan from its new sha256: line
AMBIGUOUS_ANCHORUse a paragraph ID or a more specific exact anchor
Document already contains revisionsList them with jubarte changes; explicitly accept/reject them or set an edit-plan revision policy
Comment is present in DOCX but missing from rendered markupCheck the documented comment-balloon limitations and docs/WORD_COMMENT_BALLOONS.md
Output page count is not exactly Word'sThe renderer targets Word behavior but is independent; inspect known differences before treating page count as an invariant
Prebuilt archive is absent for a platformInstall with Cargo or build from source; then check the release assets for a later tag
Need exact package differencesUse jubarte debug diff and the text, runs, xml or render debug catalogs

Contributing

Contributions that improve Word compatibility, package validity, diagnostics, documentation, examples or platform support are welcome.

Before opening a PR:

cargo fmt --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-features --no-fail-fast
cargo publish --dry-run

If you change any CLI flag, subcommand or public API, also run scripts/gen_docs.sh and commit the regenerated guide pages — the CI docs job fails when they drift.

For Python binding changes, run the Python test suite from jubarte-python.

Please include a minimal fixture or regression test for document-behavior changes. For Word-compatibility changes, describe what Microsoft Word does and how the fixture demonstrates it.

For security vulnerabilities, use a private GitHub security advisory or the private contact documented by the project rather than a public issue.

Roadmap

Proposed areas of focus:

  • Stabilize and document a 1.0 compatibility policy.
  • Keep every advertised binary/wheel target release-complete.
  • Expand newcomer examples for CLI, Rust, Python, Node and browser.
  • Improve editable coverage for currently unsupported DOCX structures.
  • Continue closing documented Word-layout differences.
  • Add a polished browser demo for compare/review/render workflows.
  • Make benchmark environments easier for third parties to reproduce.
  • Expand community documentation and contributor onboarding.

Roadmap items are goals, not compatibility guarantees.

Project structure

src/
  lib.rs                 public Rust crate
  document_comparer.rs   compare / revisions
  comparer/              comparison engine
  changes.rs             tracked-change listing / resolution
  inspect.rs             document inspection, edit-plan anchors
  edit.rs + edit/        validated JSON edit plans
  markdown/              Markdown in and out: convert, diff, patch
  convert/               DOCX → PDF/PNG layout engine
  opc/                   DOCX package layer
  xmllinq/               shared XML model
  bin/jubarte.rs         CLI (compare, edit, convert, diff, debug, …)

jubarte-python/           PyO3 / maturin Python package
jubarte-wasm/             wasm-bindgen / npm package
jubarte-app/              Tauri desktop shell: its own repository (arthrod/jubarte-app), cloned here untracked
examples/                 examples and profiling tools
tests/                    integration tests, goldens and corpus
docs/                     compatibility and engineering documentation
tools/                    validation / benchmark tooling
scripts/                  installation, release and Word-probe scripts

License

jubarte-redlines is licensed under GNU Affero General Public License v3.0 only (AGPL-3.0-only).

Review the license requirements before embedding or deploying the software in a product or network service.

Links

Engine comparison site · jandira.tech · arthur.law · Cicero · LinkedIn · contact@arthur.law

↑↓ navigate openesc close