Clause-aware drift detection between two contract versions. Pre-signature gate for legal teams and agent pipelines. Single-file Node CLI, two runtime deps, deterministic by design, no LLM tier in v1.
Part of the contract-ops CLI suite: template-vault-cli (storage) → draft-cli (fill placeholders) → nda-review-cli (review, redline, negotiate) → docx2pdf-cli (DOCX → PDF) → sign-cli (signing + audit). compare-cli is the pre-signature drift gate. Showcase site.
compare negotiated.docx ready-to-sign.pdf
# exit 0 = safe to sign
# exit 2 = substantive drift, DO NOT sign without review
# exit 3 = cosmetic / typographic drift only (informational)
# exit 4 = clauses moved, content identicalPart of the contract-ops CLI suite. draft-cli (fill placeholders) → nda-review-cli (review, redline, negotiate) → docx2pdf-cli (DOCX → PDF) → sign-cli (signing + audit). Storage: template-vault-cli. Drift detection: compare-cli (this CLI). Showcase site.
Agent pipelines: see
mcp/README.mdforcompare-cli-mcp, the MCP server wrapping this CLI. Three tools (compare_files,compare_with_negotiation,compare_demo), stdio transport, JSON-first responses. Design contract:docs/mcp.md.
You negotiated a contract over several rounds with counterparty's counsel.
A few days later their paralegal sends you a ready-to-sign.pdf. Is the
PDF the same text you agreed to, or did something quietly shift between
"final version" and "version we put in the signing envelope"?
compare-cli answers that question with an exit code your CI can gate on:
- Exit 0: every clause matches. Safe to sign.
- Exit 2: substantive drift — a clause's text changed, a clause was added, or a clause was removed. The exact change is in the report. Do not sign without review.
- Exit 3: cosmetic-only (whitespace, curly quotes, em-dashes) or
typographic-only (case,
$1,000vs$1000, Oxford comma) drift. Informational; the agreement didn't change. - Exit 4: clauses moved but content is identical. Order shifted; the agreement didn't change.
The CLI works on .docx, .pdf, .md, and plain text — including
cross-format comparisons (negotiated .docx vs ready-to-sign .pdf).
npm install -g compare-cliOr run without installing:
npx -p compare-cli@latest -- compare --demo(The package is compare-cli; the installed command is compare. The
-p … -- compare form tells npx which package to fetch and which bin
to run, since the two names differ.)
Requires Node ≥ 20. (Pinned by pdfjs-dist@^5.7.284, which we share with sign-cli.)
compare --demoRuns against two bundled fixtures (a negotiated NDA and a candidate where the term silently shifted from "two (2) years" to "three (3) years") and exits 2. You see the headline contract — substantive drift detected, report written, exit code asserted — without authoring a file.
compare --demo --jsonSame comparison, structured JSON output to stdout.
| If you are… | Start here |
|---|---|
| A new user evaluating the gate | Run this above, then the End-to-end transcript |
| An LLM agent driving the CLI | AGENTS.md → compare --catalog json → compare <base> <candidate> --json |
| Gating in CI | compare --check (exit-code only) or --sarif for code-scanning |
| Wiring via MCP | the compare-cli-mcp package — see docs/mcp.md |
# You negotiated this:
cat > negotiated.md <<'EOF'
## 1. Purpose
This agreement governs information exchange between Acme and Globex.
## 2. Term
The term is two (2) years from the Effective Date.
## 3. Notices
All notices shall be delivered to the addresses on the cover page.
EOF
# They sent back this:
cat > ready-to-sign.md <<'EOF'
## 1. Purpose
This agreement governs information exchange between Acme and Globex.
## 2. Term
The term is three (3) years from the Effective Date.
## 3. Notices
All notices shall be delivered to the addresses on the cover page.
EOF
# Gate on it:
compare negotiated.md ready-to-sign.md
# → substantive drift, exit 2
echo "exit: $?"
# Or feed the base from nda-review-cli's negotiation.json:
compare --from-negotiation negotiation.json ready-to-sign.pdf
# Strict mode: treat typographic drift ($1,000 vs $1000) as substantive too
compare negotiated.md ready-to-sign.md --strictIn a CI pipeline:
compare negotiated.docx received/$(date +%F).pdf || {
status=$?
case $status in
0) echo "✓ safe to sign" ;;
2) echo "✗ substantive drift — do not sign" ;;
3) echo "⚠ cosmetic drift only — informational" ;;
4) echo "⚠ clauses moved but content identical" ;;
*) echo "✗ I/O or arg error" ;;
esac
exit $status
}USAGE
compare BASE CANDIDATE [options]
compare --from-negotiation NEG.json CANDIDATE [options]
compare --demo [--json]
ARGS
BASE what was agreed (.docx | .pdf | .md | .txt | - for stdin)
CANDIDATE what is being put forward (same formats, or -)
OPTIONS
--from-negotiation PATH read base text from nda-review-cli's negotiation.json
--require-signoffs with --from-negotiation, require both parties to have signed off
--only-clauses PATTERNS comma-separated; only include matching clause titles in the report + exit code
--ignore-clauses PATTERNS comma-separated; exclude matching clause titles from the report + exit code
--strict treat typographic drift as substantive (exit 2)
--strict-cosmetic treat cosmetic drift as substantive (exit 2)
--json emit structured JSON to stdout
--sarif emit SARIF v2.1.0 to stdout (for code-scanning / CI annotations)
--no-intra-diff disable inline word-level diff inside substantive clauses
--why print structured explanation to stderr
--silent, -q suppress stderr
--check suppress all output; communicate via exit code only (implies --silent)
--output, -o PATH write report to PATH instead of stdout (ignored with --check)
--completion bash|zsh emit shell completion script
--demo run a zero-file 30-second demo
--version, -V print version
--help, -h show this help
| Code | Meaning |
|---|---|
0 |
No drift detected, safe to sign |
1 |
I/O error — input not found, unreadable, malformed .docx/.pdf |
2 |
Substantive drift (or --strict/--strict-cosmetic was set and tripped) |
3 |
Cosmetic-only or typographic-only drift (informational) |
4 |
Clause(s) moved but content identical |
Stable across minor versions. Documented in AGENTS.md and COMPARE_SCHEMA.md.
- cosmetic — whitespace runs, line wraps, curly vs straight quotes,
em-dashes vs hyphens, NBSP vs space, ellipsis vs
... - typographic — number formatting (
$1,000vs$1000,5.0%vs5%), Oxford comma flips, case-only differences (AcmevsACME) - substantive — any word change, addition, deletion, or clause
reorganization (
will notvswill,two yearsvsthree years, clauses added or removed) - moved — clauses whose text is byte-identical but whose position in the ordered clause sequence changed
The exact rules — including what is deliberately not normalized away (singular/plural, tense, negation, list punctuation) — are locked in COMPARE_SCHEMA.md §5–§6.
Stable across minor versions. Top-level keys: ok, exit_class,
exit_code, base, candidate, summary, differences, warnings.
See COMPARE_SCHEMA.md §10 for the full shape.
Structured key=value lines on stderr describing detection tiers,
alignment method, class counts, exit decision, and strict-mode state.
Same posture as draft-cli's --why.
Reads nda-review-cli's negotiation.json and extracts the latest
agreed text as BASE. Three-tier resolution (preferred → fallback):
top-level status: converged|signed_off|finalized, per-round
agreed: true (minimum schema), per-round clause_status all
"agreed" (historical fallback). See
COMPARE_SCHEMA.md §9.
Pair with --require-signoffs when running unattended: it errors
(exit 2) unless both signoffs.a and signoffs.b are non-empty in the
state file. nda-review-cli populates these when the negotiate sign-off
checkpoint is satisfied — a human-review gate before any agreement is
acted on.
--only-clauses Term,Payment,Indemnification keeps only matching
clauses in the report and exit-code calculation; --ignore-clauses Acknowledgments,Notices drops matching ones. Patterns are
case-insensitive substrings, matched against numbering-stripped clause
titles. Both flags can combine (--ignore-clauses runs after
--only-clauses). The number of suppressed differences is surfaced in
the human report and as summary.suppressed_by_filter in --json /
SARIF output, so the suppression is auditable.
--sarif emits a SARIF v2.1.0 document. Each substantive difference is
a result with level: error; cosmetic/typographic are warning;
added/removed/moved are note. Designed for GitHub Actions code-
scanning upload — substantive drift appears as inline annotations on
the candidate file in the PR review UI.
compare base.docx contracts/2026/acme.docx --sarif > compare.sarif
# upload via: github/codeql-action/upload-sarif@v3 with sarif_file=compare.sarif--check suppresses both stdout and stderr; the exit code is the only
output. Use for CI gates and short shell pipelines:
compare base.docx cand.docx --check && echo "safe to sign"Implies --silent. --output is also skipped under --check (you said
you only care about the exit code).
If no agreed round exists, the CLI exits 2 with a clear error.
Text extraction from .pdf is layout-lossy. When either side is a PDF,
the report surfaces a warning:
warning: candidate was extracted from PDF; layout-sensitive differences
may surface as false positives
If extraction returns zero characters (a scanned PDF without an OCR layer), compare-cli exits 1 with a clear message rather than silently reporting no drift.
Pipeline (in order):
- draft-cli — fills placeholders in templates before negotiation begins.
- nda-review-cli — owns
the
negotiation.jsonhash-chained state file.--from-negotiationreads its output. - docx2pdf-cli — the
upstream step that turns the agreed
.docxinto the.pdfyou put in the signing envelope. - sign-cli — runs the actual signing flow; compare-cli is the gate that runs before it.
Auxiliaries:
- template-vault-cli
— clause-aware template storage; produces
info --jsonpayloads that downstream tools consume. Implements the same clause-detection rule (docs/clause-detection.md) compare-cli does; divergences tracked in template-vault-cli/docs/clause-detection-divergence.md.
- COMPARE_SCHEMA.md — v1 contract. The single source of truth for what the CLI does. Implementation and tests are written against this file.
- AGENTS.md — for LLM consumers and agent pipelines. Stable output shapes, exit codes, failure diagnosis.
- ARCHITECTURE.md — how the CLI is shaped and why.
- GETTING_STARTED.md — 10-minute walkthrough.
- FAQ.md — common questions.
- SECURITY.md — disclosure policy.
- CONTRIBUTING.md — for contributors.
- CHANGELOG.md — release history.
MIT © DrBaher