The diff that answers
“what actually matters?”
Point wtd at a directory of configs, contracts, JSON — even executables. It finds the consensus, ranks the drift, flags the outlier — and shows the evidence behind every claim.
curl -fsSL https://raw.githubusercontent.com/copyleftdev/whatthediff/main/install.sh | sh
$ wtd configs/ WhatTheDiff — corpus analysis Corpus: 5 artifacts · 12 distinct primitives · 34 observations Consensus universal 2 (present in all 5 artifacts) majority 5 unique 5 consensus core: 7 primitives Drift (0 = pure consensus) 0.727 configs/svc-d.yaml ⚠ OUTLIER 0.375 configs/svc-c.yaml 0.000 configs/svc-a.yaml Evidence — unique primitives configs/svc-d.yaml (4 unique) kv admin_backdoor=enabled (line 7) kv tls=false (line 5) kv db.host=10.9.9.9 (line 3)
Files in. Meaning out.
wtd never compares raw text. Every artifact is decomposed into
primitives — stable semantic facts like db.port=5432 —
each hashed with BLAKE3, so key order, whitespace, quoting and comments never register as difference.
Facts every artifact agrees on. Your ground truth.
k = NThe consensus core — held by most, missing from a few.
2k > NFactions — and wtd names them: groups sharing the same deviations, each with its signature.
1 < k, 2k ≤ NFacts found in exactly one artifact. Where outliers hide.
k = 1Evidence, not vibes.
Meaning over syntax
JSON parsed leaf-by-leaf, YAML and configs as key paths, Markdown by structure. Reordering keys or reformatting a file changes nothing in the report.
Every claim is inspectable
Each observation keeps its full occurrence list — artifact and line. The report never asserts anything you can't open a file and check.
Deterministic to the byte
No AI in the engine, no randomness, no hidden state. Same corpus in, byte-identical report out — property-tested across 565 seeded iterations.
Fast at any scale
~23 µs per file with flat, linear scaling — measured, not estimated, from 1,000 to 200,000 artifacts. Written in Zig with streaming-first design.
One small binary
~300 KB, statically linked, zero dependencies. Fully static musl builds on Linux — drop it in a container, a CI step, or an air-gapped box.
AI-ready evidence graph
wtd --json emits the full evidence graph (wtd.report.v1) —
a stable contract for pipelines, dashboards, or an LLM that explains
why a file drifted without ever inventing facts.
Measured, and honest about the ceiling.
The benchmark plants known rogues in generated corpora, then fails unless wtd flags exactly the planted set. Speed without correctness doesn't count.
| files | planted rogues | wall time | per file | detection |
|---|---|---|---|---|
| 1,000 | 20 | 0.02 s | 20 µs | exact · 0 false positives |
| 10,000 | 200 | 0.18 s | 18 µs | exact · 0 false positives |
| 50,000 | 1,000 | 0.93 s | 19 µs | exact · 0 false positives |
| 200,000 | 4,000 | 3.88 s | 19 µs | exact · 0 false positives |
| 1,000,000 | 20,000 | 21.8 s | 22 µs | exact · 0 false positives |
ReleaseFast build, 2026-07-07 (v0.5.0 streaming evidence store). File contents live in a per-artifact arena reset after each file, so resident memory scales with distinct facts, not corpus bytes — 1M files run in 3.8 GB RSS (2.56M distinct primitives, 41.8M observations). Reproduce any row with scripts/bench.sh.
Which one is wrong — and what should it be?
Drift and factions tell you which files differ. --conflicts
answers the question ops actually asks: for a given key, what value does the fleet agree on, and
exactly which files disagree? The marks the consensus value;
every other row names the deviants — across formats, since a JSON 5432
and a YAML 5432 are one and the same fact.
$ wtd configs/ --conflicts Conflicts (scalar keys the fleet disagrees on) db.port ✓ 40× 5432 1× 5433 prod-17.yaml logging.level ✓ 38× info 3× debug staging-2.json, staging-7.json, staging-9.json 2 keys in conflict
Two exclusions keep the signal clean: list keys
(features[]) are bags, so many values are never a conflict; and a key
is only flagged when its plurality value is shared by two or more files, which drops identifier fields
(hostnames, node ids) where every file legitimately differs. Under --keys-only
values are gone entirely — so conflicts reports nothing, secret-safe by construction. Every conflict is in the
--json evidence graph too, with per-value witness sets.
Make it a CI gate: --fail-on
Turn the report into an enforcement rule. --fail-on
exits 3 when the corpus violates a policy, so a pipeline blocks the change — no dashboard, no human in the loop.
Conditions compose: conflicts, outliers,
drift>0.5. The verdict is in the JSON too (a gate
object), and --keys-only keeps it secret-safe.
# .github/workflows/config-guard.yml - name: Guard config fleet run: | curl -fsSL .../install.sh | sh wtd ./configs --fail-on 'conflicts,drift>0.5' # exit 0 pass · 3 gate failed · 1 error · 2 usage
It reads binaries too — like SSDeep, but it explains itself.
Point wtd at a folder of executables and every binary is cut into content-defined chunks — the same content-triggered piecewise hashing at the heart of SSDeep/CTPH. Each chunk is a primitive, so the same consensus, drift and faction engine clusters executables by shared code — with the exact chunks and byte offsets as evidence.
$ wtd ./samples --factions Factions (groups deviating from consensus in the same way) faction of 3 · cohesion 1.00 members: mathapp-v1, mathapp-v2, mathapp-v3 shared: chunk f722a9b73035213b… (3/3 members) faction of 3 · cohesion 1.00 members: textproc-v1, textproc-v2, textproc-v3 shared: chunk 2123887eae9ddcfe… (3/3 members)
Six stripped ELF binaries, source deleted —
two families of three variant builds each, clustered correctly from the bytes alone.
Unlike SSDeep's pairwise 0–100 score you get family clustering, shared-vs-unique regions,
and wtd ask "which are variants of
the same program?". ELF, PE, Mach-O and Wasm are recognized, so a lone PE among ELF
files is an outlier before chunk analysis even matters.
Triage on meaning, not opaque bytes
Chunks cluster binaries, but chunk a1b2c3…
tells an analyst nothing. So wtd also lifts the facts a reverse engineer actually triages on — imports,
exports, section names, needed libraries, and ASCII/UTF-16 strings — from ELF, PE, and Mach-O, and emits them
as primitives through the same engine. A network tool dropped into a folder of coreutils becomes the outlier because it
uniquely imports socket and TLS functions, surfaced as named evidence. The parsers are validated against
nm/readelf/objdump/llvm-nm
on real binaries — imports, exports, needs and sections match exactly — and bounds-checked so a hostile binary never crashes.
Turn a family into a detection rule: wtd yara
Clustering tells you binaries are related; the next step is what defines the family, and can I detect it?
wtd yara computes each family's discriminative core — features present in
every member and absent from every other sample in the corpus — and writes a candidate YARA rule from them. The soundness guarantee: an atom is
emitted only when its witness set equals the family's member set exactly, so it matches the whole family and nothing else you scanned.
It's the anti-yarGen — deterministic, every atom traced to evidence.
$ wtd yara ./samples rule wtd_family_0 { meta: description = "wtd signature for a 3-member family" members = "sampleA.bin, sampleB.bin, sampleC.bin" strings: $imp0 = "CreateRemoteThread" ascii wide $str3 = "%s\\svchost.exe" ascii wide $c7 = { e8 ?? ?? ?? ?? 8b 45 fc … } condition: 6 of them }
Same engine, pointed at secrets: --keys-only
The faction engine doesn't care what it's clustering. Point it at a folder of
.env files with --keys-only and it compares them by key schema,
not values — dropping the value from every key=value pair and hashing raw lines, so
no secret ever enters the output. It's the "which environment is missing a key,
which profiles share an auth shape?" audit, safe to run on real credentials.
$ wtd ./environments --keys-only --factions Factions (groups sharing the same key schema) faction of 3 · cohesion 1.00 members: prod.env, staging.env, dev.env shared: kv DATABASE_URL (3/3 members) shared: kv CLIENT_ID (3/3 members) shared: kv CLIENT_SECRET (3/3 members) faction of 2 · cohesion 1.00 members: db-primary.env, db-replica.env shared: kv USER_NAME (2/2 members) shared: kv PASS (2/2 members) — key names only; every value dropped before the report is built.
Documentation
Everything wtd does, in one place. The deterministic engine runs the same for every command — flags and subcommands only choose what to compute and how to print it.
Commands
Analyze a corpus and print the human report — consensus, drift, conflicts, factions, and unique-primitive evidence. Directories are walked recursively; each file is routed to an extractor by extension or content sniff.
An LLM answers your question about the corpus, grounded in the engine's evidence with file:line citations. It only ever sees engine-generated evidence — it cannot invent a finding. Works with the Anthropic API, OpenRouter, or a local OpenAI-compatible endpoint.
Emit a candidate YARA rule for each detected binary family, built from the features exclusive to its members (present in all of them, absent from every other sample). Deterministic; review before shipping.
Fetch web pages over a zero-dependency HTTP client and cluster them (phishing-kit / clone detection). URLs become the artifact names; each request has a hard --timeout (default 10 s) so dead hosts can't hang the run; --snapshot-dir saves exactly what was fetched so the analysis is reproducible. Retrieves server-rendered HTML — for JS-rendered credential forms, feed a headless-captured DOM as an .html snapshot instead.
Emit a kit signature for each detected web family — the harvested form fields, the form action host, the resource hosts, and how much of the structural skeleton is exclusive to the family. The web analog of wtd yara; machine-readable with --json.
Options
| Flag | Effect |
|---|---|
| --json | Machine-readable evidence graph (schema wtd.report.v1). |
| --evidence | With --json: emit every occurrence, no per-primitive cap. |
| --consensus | Print only the consensus buckets. |
| --drift | Print only the drift ranking + outliers. |
| --conflicts | Print only conflicts — the agreed value per scalar key and which files disagree. |
| --factions | Print only factions — groups deviating from consensus the same way. |
| --keys-only | Compare structure, not values: drop the value from every key=value primitive and hash raw lines. Secret-safe — no value reaches the report. |
| --fail-on <spec> | CI gate: exit 3 if the corpus violates the policy. <spec> is a comma-separated list of conflicts[>N], outliers[>N], drift>F. |
| --dry-run | ask only — print the exact model prompt instead of calling a model. |
| --model <m> | ask only — override the model (also honors WTD_AI_MODEL). |
| --version / --help | Print the version, or the full usage. |
Exit codes
0success — analysis ran (and any gate passed)1internal error (unreadable input, etc.)2usage error (bad flag or spec)3gate failed — a--fail-oncondition tripped
Environment — wtd ask
ANTHROPIC_API_KEY— use the Anthropic Messages APIOPENROUTER_API_KEY— use OpenRouterWTD_AI_URL— any OpenAI-compatible endpoint (e.g. a local Ollama), no key neededWTD_AI_MODEL— model override
Supported formats
| Input | Routed on | Becomes |
|---|---|---|
| JSON / JSONC | .json · sniff | key-path facts (comments & trailing commas tolerated) |
| YAML | .yaml .yml | key-path facts (unify with JSON) |
| INI / env / TOML-ish | .ini .env .conf .toml | key-path facts (export normalized) |
| XML | .xml .svg .xsd .plist | key-path facts (attributes unify with elements) |
| HTML / DOM | .html .htm · sniff | structural shingles + form fields + resource hosts (clone/kit clustering) |
| CBOR | .cbor · self-describe tag | key-path facts (unify with the JSON twin) |
.pdf | normalized text lines (FlateDecode handled) | |
| Markdown | .md | headings + text lines |
| ELF / PE / Mach-O | .exe .dll .so .dylib .bin · binary sniff | content-defined chunks + imports/exports/sections/needs/strings |
| Any binary / firmware | binary sniff | content-defined chunks + strings |
| Plain text | fallback | normalized text lines |
JSON report — --json
{
"schema": "wtd.report.v1",
"corpus": { artifacts, distinct_primitives, observations, skipped },
"consensus": { universal, majority, minority, unique, core_size },
"drift": { mean, stddev },
"gate": null | { failed, conditions:[{ metric, threshold, observed, tripped }] },
"credential_forms": [ { page, harvests:[…], posts_to, off_domain } ],
"conflicts": [ { key, holders, deviants, values:[{ value, count, artifacts }] } ],
"factions": [ { size, members, member_paths, cohesion, signature:[…] } ],
"artifacts": [ { id, path, kind, size, primitives, in_core, unique, drift, outlier } ],
"evidence": [ { identity, kind, canonical, artifacts, bucket, occurrences:[{ artifact, line }] } ]
}
Every claim in the report traces to evidence[] — identity, how many artifacts hold it, and the artifact+line of each occurrence. gate is null unless --fail-on was given, so the schema is stable.
One line. Any OS. Checksum-verified.
Linux x86_64 · aarch64, fully static
curl -fsSL https://raw.githubusercontent.com/\ copyleftdev/whatthediff/main/install.sh | sh
Installs to /usr/local/bin or ~/.local/bin.
macOS Intel · Apple Silicon
curl -fsSL https://raw.githubusercontent.com/\ copyleftdev/whatthediff/main/install.sh | sh
Same script — it detects your OS and architecture.
Windows x86_64 · aarch64
irm https://raw.githubusercontent.com/\ copyleftdev/whatthediff/main/install.ps1 | iex
PowerShell — installs and adds wtd to your user PATH.
SHA256SUMS before installing — and refuse on mismatch.
Pin a version with WTD_VERSION=v1.11.1, or grab binaries yourself from
GitHub Releases.
Questions, answered plainly.
How is this different from diff or git diff?
Those compare two files line-by-line and answer “what changed?”. wtd compares N files by meaning and answers “what actually matters?” — consensus, drift, outliers, and the evidence for each. Key order, whitespace and comments never show up as differences.
Does it use AI?
Not in the engine. Comparison, consensus, drift and outlier detection are fully deterministic — byte-identical output for the same corpus. The optional wtd ask command sends the engine's evidence to an LLM (Anthropic API, OpenRouter, or a local model via Ollama) that explains it with file-and-line citations — it can never invent a finding, because the evidence block is all it sees.
What formats does it understand?
JSON (fully parsed, incl. JSONC — comments/trailing commas, so tsconfig.json works), YAML-style configs, XML (attributes, entities, CDATA), CBOR (RFC 8949 binary — the same fact in CBOR and JSON is one identity), PDF (text extraction, FlateDecode included), binaries/executables (SSDeep-class fuzzy analysis — clusters executables into families by shared content-defined chunks, ELF/PE/Mach-O/Wasm detection), INI/env/TOML-style key-value files, Markdown, and any text file as a fallback. Formats unify: the same fact in JSON, YAML, INI, or XML hashes to the same identity — even an XML attribute matches a JSON nested key. Malformed input degrades gracefully to line-based comparison instead of failing.
How does outlier detection work?
Every primitive held by a strict majority of files forms the consensus core. A file's drift is 1 − Jaccard(its primitives, core); anything past mean + 1.5σ is flagged. In benchmarks with planted rogues, detection is exact at every corpus size — 4,000 of 4,000 found at 200k files, zero false positives.
Can it compare credential or .env profiles safely?
Yes — --keys-only compares by
structure, not content: it drops the value from every key=value
primitive and hashes raw lines, so no secret enters the report. Point it at a folder of
.env files to find schema drift —
which environment is missing a key, which profiles share an auth shape — without exposing a
single value. Shell export KEY=… is
normalized to KEY.
Can it compare binaries or executables?
Yes — see Beyond text above. Executables are cut into content-defined chunks (the technique inside SSDeep/CTPH fuzzy hashing), so the same faction engine clusters them into families of variants. Unlike SSDeep's pairwise score you get clustering, the shared and unique regions with byte offsets, and an AI explanation. ELF, PE, Mach-O and Wasm formats and architectures are detected.
Can I trust the binaries?
Releases are built in a public GitHub Actions workflow from tagged source, and every asset ships with a SHA256SUMS file. The install scripts verify checksums before installing and refuse on mismatch. It's also ~1,900 lines of Zig — small enough to read.
What's on the roadmap?
Semantic source-code extractors. Already shipped: SSDeep-class binary/executable fuzzy analysis (v0.8.0 — content-defined chunking clusters executables into families, validated on real compiled ELF variants), the PDF text extractor (v0.7.0), the XML extractor (v0.6.0 — attributes unify with child elements), the streaming evidence store (v0.5.0 — one million files in 21.8 s and 3.8 GB RSS, detection still exact), wtd ask (v0.2.0, the AI adapter over the evidence graph), cross-format canonical unification (v0.3.0 — the same fact in JSON, YAML, or INI hashes to the same identity), and faction detection (v0.4.0 — groups deviating from consensus together, with their defining signatures).