Peek, the WhatTheDiff mascot WhatTheDiff
Deterministic semantic diff · v1.11.1

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
Peek, the WhatTheDiff mantis shrimp mascot, peeking over the terminal
$ 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)
22 µsper file, flat to 1M files
1,000,000files analyzed in 21.8 s
20,000 / 20,000planted outliers found, 0 false positives
0dependencies — one static binary
byte-identicalsame corpus in, same report out
How it works

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.

artifacts normalize extract primitives canonical form BLAKE3 identity evidence store consensus drift report
universal

Facts every artifact agrees on. Your ground truth.

k = N
majority

The consensus core — held by most, missing from a few.

2k > N
minority

Factions — and wtd names them: groups sharing the same deviations, each with its signature.

1 < k, 2k ≤ N
unique

Facts found in exactly one artifact. Where outliers hide.

k = 1
drift(A) = 1 − Jaccard(A, consensus core) · outlier ⇢ mean + 1.5σ
Principles

Evidence, 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.

Benchmarks

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.

Benchmark results by corpus size
filesplanted rogueswall timeper filedetection
1,000200.02 s20 µsexact · 0 false positives
10,0002000.18 s18 µsexact · 0 false positives
50,0001,0000.93 s19 µsexact · 0 false positives
200,0004,0003.88 s19 µsexact · 0 false positives
1,000,00020,00021.8 s22 µsexact · 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.

The odd-one-out report

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
Beyond text

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.

Peek in RE mode — the mascot suited up with a scanner cannon and targeting reticle for hunting through binaries
Peek, in RE mode.

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.
Reference

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

wtd <path>…

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.

wtd ask "<question>" [path…]

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.

wtd yara <path>…

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.

wtd web <url>… [--timeout <sec>] [--snapshot-dir <dir>]

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.

wtd kit <path>…

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

FlagEffect
--jsonMachine-readable evidence graph (schema wtd.report.v1).
--evidenceWith --json: emit every occurrence, no per-primitive cap.
--consensusPrint only the consensus buckets.
--driftPrint only the drift ranking + outliers.
--conflictsPrint only conflicts — the agreed value per scalar key and which files disagree.
--factionsPrint only factions — groups deviating from consensus the same way.
--keys-onlyCompare 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-runask only — print the exact model prompt instead of calling a model.
--model <m>ask only — override the model (also honors WTD_AI_MODEL).
--version / --helpPrint the version, or the full usage.

Exit codes

  • 0 success — analysis ran (and any gate passed)
  • 1 internal error (unreadable input, etc.)
  • 2 usage error (bad flag or spec)
  • 3 gate failed — a --fail-on condition tripped

Environment — wtd ask

  • ANTHROPIC_API_KEY — use the Anthropic Messages API
  • OPENROUTER_API_KEY — use OpenRouter
  • WTD_AI_URL — any OpenAI-compatible endpoint (e.g. a local Ollama), no key needed
  • WTD_AI_MODEL — model override

Supported formats

InputRouted onBecomes
JSON / JSONC.json · sniffkey-path facts (comments & trailing commas tolerated)
YAML.yaml .ymlkey-path facts (unify with JSON)
INI / env / TOML-ish.ini .env .conf .tomlkey-path facts (export normalized)
XML.xml .svg .xsd .plistkey-path facts (attributes unify with elements)
HTML / DOM.html .htm · sniffstructural shingles + form fields + resource hosts (clone/kit clustering)
CBOR.cbor · self-describe tagkey-path facts (unify with the JSON twin)
PDF.pdfnormalized text lines (FlateDecode handled)
Markdown.mdheadings + text lines
ELF / PE / Mach-O.exe .dll .so .dylib .bin · binary sniffcontent-defined chunks + imports/exports/sections/needs/strings
Any binary / firmwarebinary sniffcontent-defined chunks + strings
Plain textfallbacknormalized 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.

Install

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.

Both installers verify the download against the release's published SHA256SUMS before installing — and refuse on mismatch. Pin a version with WTD_VERSION=v1.11.1, or grab binaries yourself from GitHub Releases.
FAQ

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).