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.
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
README
synced Oct 25, 2026See 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.
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,
jubarteCLI reference - Python guide — wheel and
uvx, the three library tiers, generated API reference - Node and browser guide —
jubarte-wasmandnpx 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.
| Host | Install |
|---|---|
| Claude Code | claude mcp add --transport stdio jubarte --scope project -- uvx --from 'jubarte-redlines[mcp]' jubarte-mcp --root . |
| Codex | codex mcp add jubarte -- uvx --from 'jubarte-redlines[mcp]' jubarte-mcp --root . |
| Gemini CLI | gemini 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.
| Command | What it does |
|---|---|
jubarte ORIGINAL MODIFIED | Read, edit, compare and render Word documents |
jubarte compare | Compare documents: print the redline as the agent view, or write it with -o [alias: redline] |
jubarte changes | List tracked changes with IDs for accept, reject and edit plans |
jubarte accept | Accept all tracked changes, or select by ID, author or kind |
jubarte reject | Reject all tracked changes, or select by ID, author or kind |
jubarte convert | Convert Word or Markdown to DOCX, PDF, PNG or Markdown |
jubarte diff | Review differences as GitHub, word, normal, context or side-by-side text |
jubarte read | Read the agent view: YAML header, <!-- pN --> id lines, changes and comments with ids |
jubarte edit | Edit 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 add | Add 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 capabilities | What this binary can do, for agents choosing an operation |
jubarte visual-diff | Compare rendered pages pixel by pixel (different pages: exit 5) |
jubarte comments | List comments, threads and the text they annotate |
jubarte append | Join documents in order, preserving images, styles, lists and notes |
jubarte validate | Check Word validity, tracked edits, accessibility, style and structure; or repair Word validity |
jubarte check | Check a document against a source-bound expectation contract |
jubarte scrub | Remove authors, editing IDs, metadata and comments before sharing |
jubarte self-update | Check 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:
-o | Writes |
|---|---|
.docx (or no or an unknown extension) | the redline |
.md, .markdown, .txt | the agent view of the redline; CriticMarkup when both inputs are Markdown |
.pdf | the redline, rendered (--revisions, --pages, --changed-pages apply) |
.png | one STEM-page-NN.png per page |
.json | the 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:
| Option | Purpose |
|---|---|
-b, --original FILE | Original document |
-m, --modified FILE | Modified document |
-o, --output FILE | Write the redline; the extension picks the format (above) |
-a, --author NAME | Revision author (default: Redline) |
-d, --datetime ISO8601 | Revision timestamp (default: now, UTC) |
--detail-threshold RATIO | Comparison-detail tuning |
| `--mode word | powertools` |
--force | Replace an existing output |
--reference-doc FILE, --critic, --resource-path DIR | Markdown inputs |
--revisions, --dpi, --pages, --changed-pages | Rendering, for -o FILE.pdf or .png |
-q, --quiet | No 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]
| Option | Purpose |
|---|---|
-o, --output FILE | Output path; the extension picks the format: .docx, .md, .pdf, .png (pages named STEM-page-NN.png) or .json (the snapshot) |
--dpi DPI | PNG resolution; default 96 |
--compress | Deflate PDF content streams |
| `--revisions conventional | word |
--keep-nested-comment | With --track-changes accept or reject, keep comment threads whose whole anchor is removed, on an empty anchor; off by default |
--revision-palette SPEC | Custom change colors/lines |
--font-report FILE | Write font-resolution JSON |
--report FILE | Write page/render report |
--force | Replace existing output |
--fail-on-substitution | Exit 4 when a requested font was substituted |
--pages SPEC | Only these pages, for example 1-3,7, in the PDF and the PNGs alike; PNGs keep the document's page numbers |
--move-comments | List comments after the last page; the text keeps its tint and a [JR1] marker |
--changed-pages | Keep 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, --to | docx, 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.
| Option | Purpose |
|---|---|
--json | JSON Lines: one object per finding, nothing when there is none |
--rules SETS | word (the default), edits, a11y, style, structure, or rule codes, comma-separated |
--strict | Fail on warnings too |
--repair FILE | Write the repaired package; remaining findings still exit 2 |
--original FILE | Audit tracked edits against this original (needs --author) |
--author NAME | The author every change must carry |
--force | Replace 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):
| Kind | What it does |
|---|---|
replace | Replace 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 |
insert | Insert text after or before a match, or at position start/end |
delete | Delete a match |
rewrite | Give a paragraph's whole new text; only the words that differ are edited |
format_run | Restyle existing text as a tracked formatting change |
comment | Comment on a match, a whole paragraph, or every paragraph up to through |
reply_comment, resolve_comment, edit_comment, delete_comment | Work on a comment thread by comment_id; edit_comment on another author's comment needs "rewrite_others_comments": true (it keeps their name) |
strike | Reject another author's insertion revision visibly: your tracked deletion inside it, with an optional comment on the struck words |
restore | Reject another author's deletion revision visibly: your tracked copy of its words right after it, with an optional comment |
insert_paragraph | A new paragraph after the anchor, with its properties (or those like selects) |
delete_paragraph | Delete a paragraph, optionally commenting on the deleted text |
format_paragraph | Style, alignment, line spacing, space before and after, as a tracked property change |
merge_paragraphs | Join the next paragraph onto this one |
insert_table | A table before or after a body paragraph, tracked as inserted rows |
list | Bulleted, numbered or lettered list, tracked as property changes |
insert_footnote | A footnote after an anchor |
insert_image | A picture paragraph (PNG, JPEG, GIF, BMP or TIFF) |
insert_toc | A table of contents field; with "update_fields": true it is filled from the headings |
page_setup | Page size, orientation and margins |
watermark | Word's diagonal text watermark in every default header (untracked) |
fill_control | Fill a content control (text, choice, checkbox or date) by id, tag or alias |
redact | Replace 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 |
settings | trackRevisions, 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 warningson 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-runcargo-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
| Surface | Supported/tested target |
|---|---|
| Rust / CLI | CI tests Linux, macOS and Windows |
| Rust toolchain | Rust 1.94+ |
| Python | CPython 3.10+ |
| Python release wheels | macOS x86_64/arm64, manylinux_2_28 x86_64/arm64, musllinux_1_2 x86_64/arm64, Windows x86_64 (abi3, CPython 3.10+) |
| Node | Node 18+ |
| Browser | WebAssembly builds |
| CLI release workflow | Linux 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
| Problem | What to check |
|---|---|
| PDF layout differs from Word | Verify required fonts; inspect --font-report; read docs/WORD_DIFFERENCES.md |
| Text is missing or wraps differently | Check font resolution and embedded/system font availability |
| Edit exits with a refusal | Read report.jsonl / stdout for STALE_SOURCE, ANCHOR_NOT_FOUND, AMBIGUOUS_ANCHOR, OVERLAPPING_EDITS, UNSUPPORTED_STRUCTURE, etc. |
STALE_SOURCE | Re-run jubarte read and regenerate the plan from its new sha256: line |
AMBIGUOUS_ANCHOR | Use a paragraph ID or a more specific exact anchor |
| Document already contains revisions | List them with jubarte changes; explicitly accept/reject them or set an edit-plan revision policy |
| Comment is present in DOCX but missing from rendered markup | Check the documented comment-balloon limitations and docs/WORD_COMMENT_BALLOONS.md |
| Output page count is not exactly Word's | The renderer targets Word behavior but is independent; inspect known differences before treating page count as an invariant |
| Prebuilt archive is absent for a platform | Install with Cargo or build from source; then check the release assets for a later tag |
| Need exact package differences | Use 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.0compatibility 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