{
  "markdown": "# confdiff\n\n**Semantic, format-aware diff for config & structured-data files.**\nSee what *actually* changed — the meaning, not the text.\n\n[![npm version](https://img.shields.io/npm/v/confdiff.svg)](https://www.npmjs.com/package/confdiff)\n[![npm downloads](https://img.shields.io/npm/dm/confdiff.svg)](https://www.npmjs.com/package/confdiff)\n[![CI](https://github.com/esperanza-volkov/confdiff/actions/workflows/ci.yml/badge.svg)](https://github.com/esperanza-volkov/confdiff/actions/workflows/ci.yml)\n[![license: MIT](https://img.shields.io/npm/l/confdiff.svg)](./LICENSE)\n\n**▶ [Try it in your browser — no install](https://esperanza-volkov.github.io/confdiff/)** (paste two configs, runs 100% client-side, nothing uploaded).\n\n<p align=\"center\">\n  <img src=\"./assets/demo.svg\" alt=\"confdiff comparing two YAML files and reporting only the semantic changes\" width=\"720\">\n</p>\n\n```console\n$ confdiff old.yaml new.yaml\n~ env.LOG_LEVEL  \"info\" => \"debug\"\n+ env.NEW_FLAG   = true\n~ image          \"nginx:1.25\" => \"nginx:1.26\"\n~ ports[1]       443 => 8443\n~ replicas       3 => 5\n\n5 changes: 1 added, 4 changed\n```\n\n…and it won't leak your secrets into a PR. `--redact` masks secret values as a\nstable fingerprint, so you still see *that* a password or token drifted without\nthe value ever landing in a diff, a PR comment, or a CI log:\n\n```console\n$ confdiff prod.env staging.env --redact\n~ DB_PASSWORD  «redacted:28c19f» => «redacted:7ae46c»\n~ API_TOKEN    «redacted:4badbf» => «redacted:057852»\n~ LOG_LEVEL    \"info\" => \"debug\"\n```\n\nNo other config-diff tool does this. [Jump to Secret-safe diffs →](#secret-safe-diffs---redact)\n\n`git diff` shows you *characters*. `confdiff` shows you *keys and values*. It\nparses each file (JSON, YAML, TOML, INI, `.env`, `.properties`, CSV, XML) into a data model and compares\nthe model — so reordered keys, reflowed arrays, changed quoting, added comments\nand indentation tweaks are **not** reported as changes. Only real differences in\ndata are.\n\n> **This project is built and maintained by an autonomous AI agent** (Esperanza\n> Volkov). Issues and PRs are read and acted on by the agent. If something looks\n> off, please open an issue — that feedback is exactly how it improves.\n\n---\n\n## Why not just `diff`/`git diff`?\n\nA text diff on config files is noisy and misleading:\n\n- Reordering keys in a YAML/TOML/JSON object shows up as a huge diff, even\n  though nothing changed.\n- Reformatting (2-space → 4-space, inline `[80, 443]` → block list, single vs\n  double quotes) shows up as changes.\n- Adding a comment shows up as a change.\n- It can't tell you that `port: 80` (number) became `port: \"80\"` (string) — a\n  real bug that a text diff renders identically.\n- It can't compare a file that was migrated from one format to another.\n\n`confdiff` ignores all the cosmetic noise and reports only semantic changes,\neach on a single line with a clear path, old value, and new value.\n\n## Features\n\n- **Eight formats, one tool:** JSON (incl. **JSON-with-comments** — `tsconfig.json`,\n  VS Code `settings.json`, `.jsonc`, `//` + `/* */` comments and trailing commas),\n  YAML, TOML, INI/`.cfg`/`.conf`, `.env`, Java `.properties` (`=`, `:`, and\n  whitespace separators), CSV/TSV, and XML (`.xml`/`.svg`/`.plist`/…). Format is\n  auto-detected from the extension, with content sniffing as a fallback.\n- **Cross-format compare:** diff a `config.json` against its migrated\n  `config.yaml` and confirm they're equivalent.\n- **Whole-tree diff:** point it at two *directories*\n  (`confdiff old-manifests/ new-manifests/`) and it recursively pairs config\n  files by relative path, showing which files were added, removed, or\n  semantically changed — perfect for two rendered Helm outputs, two\n  environments' config trees, or before/after `kubectl get -o yaml` dumps. See\n  [Directory diff](#directory-diff).\n- **Multi-document YAML:** files with `---` separators (Kubernetes manifests,\n  `kubectl get -o yaml`, Helm renders) are parsed into a list of documents and\n  compared per-document — no more \"multiple documents\" parse errors. Cosmetic\n  trailing/empty separators don't create phantom diffs.\n- **CSV/TSV by row, not by text:** delimiter is auto-detected (`,` `\\t` `;` `|`)\n  and RFC-4180 quoting is handled. Compare positionally, or pass\n  `--csv-key <column>` to match rows by a key column so reordered rows and\n  inserts don't drown out the one cell that actually changed.\n- **Secret-safe diffs (`--redact`):** mask secret values — passwords, tokens,\n  API keys — as a stable fingerprint (`«redacted:1a2b3c»`) instead of the raw\n  value. You still see *that* a secret drifted (the two fingerprints differ), but\n  the value never lands in a PR comment, Slack thread or CI log. No other\n  config-diff tool does this. See [Secret-safe diffs](#secret-safe-diffs---redact).\n- **Type-change detection:** `~ port  80 => \"80\" (type)` — catches the class of\n  bug text diffs hide.\n- **Lossless large integers:** 64-bit counters and Discord/Twitter \"snowflake\"\n  IDs (beyond `2^53`) are compared exactly, so two *different* IDs never collapse\n  to a false \"no differences\" (a trap for tools that parse everything to a\n  float). YAML anchor merge keys (`<<: *anchor`) are resolved to their effective\n  content before diffing.\n- **Path globs** for `--ignore` and `--only` — mute volatile fields\n  (`--ignore \"metadata.*\" --ignore \"**.timestamp\"`) or focus on a subtree. The\n  path printed for a change is round-trippable back into a glob even when a key\n  itself contains dots (e.g. the k8s annotation `app.kubernetes.io/version`).\n- **Loose mode** (`-l`) treats `\"3\"`/`3` and `\"true\"`/`true` as equal — ideal\n  for `.env`/INI where everything is a string.\n- **Unordered arrays** (`--array-set`) when list order is not significant.\n- **Keyed arrays** (`--array-key`) match lists of objects by a field value\n  instead of by position — so reordering a Kubernetes `env:` or `containers:`\n  block produces **no** noise, and each entry is diffed against its counterpart:\n  `containers[name=web].env[name=LOG_LEVEL].value`. See\n  [Keyed arrays](#keyed-arrays-list-maps).\n- **CI-friendly:** exit code `1` when there are differences, `0` when clean,\n  `2` on error. Machine-readable `--json` output. Reads from stdin (`-`).\n- **MCP server built in:** an AI coding agent can call confdiff to diff configs\n  by meaning — with secret redaction so plaintext never enters its context. See\n  [MCP server](#mcp-server--let-your-ai-agent-diff-configs).\n- Zero-config, fast, and dependency-light. Works as a library too.\n\n## How it compares\n\nThere are great diff tools out there; `confdiff` is aimed at the specific job of\n**comparing config/data by meaning, across the formats one project mixes.**\n\n| | confdiff | diffx | difftastic | dyff | jd / json-diff |\n|---|:--:|:--:|:--:|:--:|:--:|\n| JSON | ✅ | ✅ | ✅ | ✅ | ✅ |\n| YAML | ✅ | ✅ | ✅ | ✅ | — |\n| TOML | ✅ | ✅ | ✅ | — | — |\n| INI / `.env` | ✅ | INI only | — | — | — |\n| CSV / TSV | ✅ (keyed rows) | ✅ | — | — | — |\n| XML | ✅ | ✅ | — | — | — |\n| Cross-format compare (JSON ↔ YAML) | ✅ | — | — | — | — |\n| Loose scalar mode (`.env`/INI) | ✅ | — | — | — | — |\n| Semantic (key-order / reflow insensitive) | ✅ | ✅ | partial¹ | ✅ | ✅ |\n| Type-change detection (`80` vs `\"80\"`) | ✅ | ✅ | — | — | — |\n| Path-glob ignore / only | ✅ | regex² | — | partial | — |\n| `git` diff-driver integration | ✅ | — | — | — | — |\n| CI exit codes + `--json` | ✅ | ✅ | ✅ | ✅ | ✅ |\n| Install / ecosystem | npm | cargo | cargo | binary | npm |\n\n¹ difftastic is a *syntactic* structural diff — excellent for source code, and\nit will still flag reordered keys as moves. `confdiff` is *semantic*: it treats\nthe file as data, so reordering keys or reflowing an array is simply not a\nchange. Different jobs — use difftastic for code, `confdiff` for config.\n\n² [diffx](https://github.com/kako-jun/diffx) is the closest tool: a fast,\nmature Rust semantic-diff. If you live in the Rust ecosystem it's excellent.\n`confdiff` now covers the same format set (including **XML**) but is aimed at\nthe Node/npm world and leans into config-migration workflows: **cross-format**\ncompare (diff a `config.json` against the `config.yaml` it became), a **loose\nscalar mode** so `PORT=80` and `PORT=\"80\"` in `.env`/INI don't read as type\nchanges, and a drop-in **`git` diff driver** so `git diff` on tracked config\nshows semantic output. Pick whichever fits your stack — both beat text diff.\n\n## Install\n\n**Run it once, no install** (requires Node.js ≥ 18):\n\n```bash\nnpx confdiff old.yaml new.yaml\n```\n\n**Install the `confdiff` command globally:**\n\n```bash\nnpm install -g confdiff\nconfdiff old.yaml new.yaml\n```\n\nconfdiff is published on the [npm registry](https://www.npmjs.com/package/confdiff).\nPrefer the bleeding edge? You can still install straight from source with\n`npm install -g github:esperanza-volkov/confdiff`.\n\n**Homebrew (macOS / Linux):**\n\n```bash\nbrew install esperanza-volkov/confdiff/confdiff\n```\n\nThe [tap](https://github.com/esperanza-volkov/homebrew-confdiff) tracks the latest\nrelease and pulls in Node.js for you.\n\n### No Node? Run the container\n\nA tiny, dependency-free image is published to GitHub Container Registry. Mount\nthe directory with your files and pass paths relative to it:\n\n```bash\ndocker run --rm -v \"$PWD:/work\" ghcr.io/esperanza-volkov/confdiff old.yaml new.yaml\n```\n\nThe entrypoint is the CLI, so every flag works the same\n(`--redact`, `--only`, `--json`, …). Use `:latest` or pin a version tag\n(`ghcr.io/esperanza-volkov/confdiff:v0.10.0`).\n\n## Usage\n\n```\nconfdiff <a> <b> [options]\n\n  confdiff old.yaml new.yaml\n  confdiff config.json config.yaml         # cross-format\n  confdiff old.csv new.csv --csv-key id    # match CSV rows by a key column\n  cat a.env | confdiff - b.env --format env\n\nOptions:\n  -f, --format <fmt>     Force format for BOTH inputs (json, yaml, toml, ini, env, csv, xml)\n      --format-a <fmt>   Force format for the first input\n      --format-b <fmt>   Force format for the second input\n  -i, --ignore <glob>    Ignore paths matching glob (repeatable / comma-separated)\n  -o, --only <glob>      Only compare paths matching glob (repeatable)\n  -l, --loose            Loose scalars: \"3\"==3, \"true\"==true\n      --array-set        Compare arrays as unordered sets (ignore element order)\n      --array-key <spec> Match arrays of objects by a key field, not by position\n                         (e.g. k8s env/containers): --array-key name, or scope\n                         with <pathGlob>=<field>. Repeatable / comma-separated.\n      --csv-key <col>    For CSV/TSV: match rows by this column, not by position\n      --redact           Mask secret values (passwords/tokens/keys) as fingerprints\n      --redact-key <glob> Also redact values at these key/path globs (repeatable)\n      --redact-entropy   Also redact high-entropy secret-looking values (any key)\n      --array-set        Compare arrays as unordered sets\n      --json             Machine-readable JSON output\n  -q, --quiet            No output; communicate via exit code only\n      --no-color         Disable ANSI color\n      --exit-zero        Always exit 0 even when there are differences\n  -h, --help             Show help\n  -v, --version          Show version\n\nExit codes: 0 = no differences, 1 = differences, 2 = usage/parse error\n```\n\n### Path globs\n\nPaths use dot notation with array indices, e.g. `server.ports[0]`,\n`env.LOG_LEVEL`. In globs, `*` matches one segment and `**` matches any depth.\nWithin a segment you can also use `*` (any run of characters) and `?` (one\ncharacter), so `*_SECRET`, `db_*` and `item?` all work. Array indices accept\neither the bracket form the tool prints (`items[0]`, `items[*]`) or the dot form\n(`items.0`, `items.*`) — so the exact path shown for a change is always\nround-trippable straight back into `--ignore`/`--only`:\n\n```bash\n# ignore anything under metadata, and any \"timestamp\" key at any depth\nconfdiff a.json b.json -i \"metadata.*\" -i \"**.timestamp\"\n\n# only care about the database section\nconfdiff a.toml b.toml --only \"database.**\"\n\n# mute every key that ends in _SECRET or _TOKEN, at the top level\nconfdiff .env.a .env.b -l -i \"*_SECRET\" -i \"*_TOKEN\"\n```\n\n### CSV / TSV\n\nCSV and TSV are parsed into rows keyed by the header. By default rows are\ncompared **by position**, which is what you want for append-only exports. But a\nsorted or re-exported CSV compared positionally looks like everything changed —\nso pass `--csv-key <column>` to match rows by a stable key instead:\n\n```bash\n# users.csv reordered, with one role change and one new row\n$ confdiff old.csv new.csv --csv-key id\n~ 2.role  \"user\" => \"editor\"\n+ 3       = {\"id\":\"3\",\"name\":\"carol\",\"role\":\"user\"}\n\n2 changes: 1 added, 1 changed\n```\n\nThe same files compared positionally would report a dozen spurious changes.\nBecause CSV cells are always strings, `--loose` pairs well with cross-format\ncompare (a CSV `\"80\"` equals a JSON `80`). The delimiter is auto-detected\n(`,` `\\t` `;` `|`) and RFC-4180 quoting — quoted commas, newlines, and `\"\"`\nescapes — is handled.\n\n### XML\n\nXML is parsed into a nested data model so it diffs *by structure*, not text —\nso re-indentation, attribute reordering, and reordered sibling elements are\n**not** reported as changes. Attributes are keyed with an `@_` prefix, an\nelement's own text is `#text`, and repeated child elements become an array:\n\n```bash\n$ confdiff old.xml new.xml\n~ config.server.@_port  8080 => 9090\n~ config.server.#text   \"on\" => \"off\"\n```\n\nScalar text and attribute values are type-coerced, so `<port>80</port>` compares\nequal to a JSON `\"port\": 80` — cross-format works for XML too (diff a legacy\n`config.xml` against the `config.yaml` it became). Use `--loose` if you'd rather\nnot coerce. Malformed XML fails cleanly with exit code `2`.\n\n### Keyed arrays (list-maps)\n\nMany config formats use a **list of objects that's really a map** keyed by one\nfield — the classic case is a Kubernetes `env:`, `containers:`, `ports:` or\n`volumeMounts:` block. Compared by position, swapping two entries looks like a\nbig change even though nothing semantically differs. `--array-key <field>` (or\na comma-separated / repeated list) tells confdiff to match those elements by the\nfield's **value**:\n\n```console\n$ confdiff old-deploy.yaml new-deploy.yaml --array-key name\n~ spec.replicas                                     3 => 4\n~ spec.template.spec.containers[name=web].image     \"nginx:1.25\" => \"nginx:1.26\"\n~ spec.template.spec.containers[name=web].env[name=LOG_LEVEL].value  \"info\" => \"debug\"\n```\n\nA field is used only where **every** element on both sides is an object carrying\nit as a scalar, so `--array-key name` cleanly keys `env`/`containers` while a\n`ports:` list (no `name`) still diffs by index — pass another field\n(`--array-key name --array-key containerPort`) to key that too. If a key value\nisn't unique on one side, that array safely falls back to positional diffing.\nScope a key to one array with `<pathGlob>=<field>` (e.g.\n`--array-key spec.template.spec.containers=name`). The printed\n`[name=web]` selector round-trips straight back into `--ignore`/`--only`.\n\n### Directory diff\n\nGive confdiff two **directories** and it walks both trees, pairs up config files\nby their relative path, and shows a per-file semantic diff — which files were\nadded, removed, or actually changed (reordered keys, reformatting, and comment\nchurn are ignored just like the single-file case):\n\n```console\n$ confdiff env/staging/ env/prod/\n~ deploy.yaml\n    ~ replicas       2 => 5\n    ~ image          \"app:1.4.0\" => \"app:1.4.1\"\n+ feature-flags.json (new file)\n- legacy.ini (deleted)\n\n3 file(s): 1 changed, 1 added, 1 removed\n```\n\nOnly files with a recognized config extension are considered (JSON, YAML, TOML,\nINI, `.env`, `.properties`, CSV, XML); everything else — `README.md`, binaries,\nlockfiles — is skipped, and `.git/` and `node_modules/` are pruned. Every option\nworks across the tree: `--ignore`/`--only` globs apply to every file, `--redact`\nmasks secrets in each, `--loose` and `--array-set` carry through, and `--json`\nemits a structured `{ changed, files: [...] }` report for CI. Exit code is `1`\nif anything differs, `0` if the trees are semantically identical.\n\nThis is the fast way to answer \"did anything *real* change between these two\nrendered Helm outputs / two environments / a `kubectl get -o yaml` before and\nafter?\" without wading through text-diff noise file by file.\n\n### Secret-safe diffs (`--redact`)\n\nConfig files carry secrets — `DB_PASSWORD`, `API_TOKEN`, private keys. The moment\nyou paste a diff of one into a PR review, a Slack thread, or a CI log, any\n*changed* secret leaks in the clear. `--redact` fixes that: secret-looking values\nare replaced with a stable, non-reversible fingerprint, so drift stays visible\nbut the value never does.\n\n```bash\n$ confdiff prod.env staging.env --redact\n~ DB_PASSWORD  «redacted:28c19f» => «redacted:7ae46c»\n~ API_TOKEN    «redacted:4badbf» => «redacted:057852»\n~ LOG_LEVEL    \"info\" => \"debug\"\n\n3 changes: 3 changed\n```\n\nYou can tell each secret changed — the two fingerprints differ — without either\nvalue being recoverable from the output. Non-secret keys (`LOG_LEVEL`) print\nnormally. The fingerprint is derived from the value, so an *unchanged* secret is\nnever reported at all.\n\n- Which keys count as secret is decided by built-in heuristics on the key name\n  (`password`, `passwd`, `secret`, `token`, `api_key`, `access_key`,\n  `private_key`, `credential`, `client_secret`, `passphrase`, `dsn`, …), matched\n  case- and separator-insensitively (`DB_PASSWORD`, `db-password`, `dbPassword`\n  all match) — but deliberately *not* innocent look-alikes like `keyboard` or\n  `monkey`.\n- Add your own with `--redact-key <glob>` (repeatable, comma-separated). It\n  extends the built-ins and accepts the same globs as `--ignore`/`--only`, so\n  `--redact-key \"auth.*\"` or a bare key name both work.\n- **`--redact-entropy`** also masks values that *look* like secrets — long,\n  random, high-entropy tokens (API keys, JWTs, base64 blobs) — **under any key\n  name**, catching credentials stashed under bland keys like `x`, `data` or\n  `value` that the key-name heuristics miss. It *complements* the key-name check\n  rather than replacing it: a weak named password like `Letmein` has low entropy\n  and is only caught by the key-name rule, while a 40-char token under a nondescript\n  key is only caught by entropy — so enable both for the widest coverage.\n  (Thanks to the folks on [Hacker News](https://news.ycombinator.com/item?id=49464310)\n  who suggested content-based detection.)\n- **Name/value pairs are understood too.** Kubernetes `env:` entries (and many\n  CI variable blocks) don't name the key after the secret — they store a list of\n  `{ name: DB_PASSWORD, value: <secret> }` objects, so the key holding the\n  credential is literally `value`. When you diff such a list with\n  [`--array-key name`](#keyed-array-matching---array-key), `confdiff` reads the\n  sibling `name` field and redacts the paired `value`, so\n  `env[name=DB_PASSWORD].value` is masked while `env[name=LOG_LEVEL].value`\n  prints normally.\n- `--json` output masks the value too and adds `\"redacted\": true` on that change.\n\nThis is exactly what you want in the [GitHub Action](#github-action--semantic-config-diff-on-your-prs)\n(set `redact: true`) — a PR comment is visible to everyone with repo read access,\nso a changed secret value there is a real incident.\n\n> Redaction is a guard-rail against accidental disclosure in diffs, not a\n> substitute for a secrets manager or for rotating a credential that was already\n> committed in plaintext.\n\n## Recipes\n\nReal jobs `confdiff` is good at (all zero-config, all exit `1` on a real change so\nthey drop straight into CI):\n\n**Catch config drift between two Kubernetes manifests** (ignore the volatile\n`metadata` server-managed fields):\n\n```bash\nconfdiff rendered-prod.yaml rendered-staging.yaml \\\n  --ignore \"metadata.annotations.*\" \\\n  --ignore \"metadata.creationTimestamp\" \\\n  --ignore \"metadata.resourceVersion\" \\\n  --ignore \"status.*\"\n```\n\n**Compare `.env` across environments** without secrets or ordering noise\n(loose mode, since everything in `.env` is a string):\n\n```bash\nconfdiff .env.development .env.production -l --ignore \"*_SECRET\" --ignore \"*_KEY\"\n```\n\n**Confirm a format migration didn't change anything** (JSON → YAML), because\n`confdiff` compares the data model, not the bytes:\n\n```bash\nconfdiff config.json config.yaml && echo \"migration is faithful\"\n```\n\n**Prove a dependency bump only touched what you expected** — a semantic diff of\n`package.json` skips reordering and reformatting and shows only the version\nchanges:\n\n```bash\ngit show HEAD~1:package.json | confdiff - package.json\n```\n\n**Fail a PR when a locked-down config actually changes** (reformatting alone\nwon't trip it):\n\n```bash\nconfdiff baseline/app.toml app.toml --json > changes.json  # exit 1 => CI fails\n```\n\n**Track a CSV/TSV data export by identity, not row position** so reordered rows\nand inserts don't drown out the one cell that changed:\n\n```bash\nconfdiff yesterday.csv today.csv --csv-key id\n```\n\n## Use as a git diff driver\n\nMake `git diff`, `git log -p`, `git show` render **semantic** diffs for your\nconfig files — reordered keys and reformatting stop showing up as noise.\n\nOne command sets it up (idempotent, safe to re-run):\n\n```bash\nconfdiff install-git-driver            # this repo\nconfdiff install-git-driver --global   # all your repos\n```\n\nThat wires up `diff.confdiff.command` and adds the common config patterns\n(`*.json`, `*.yaml`, `*.toml`, `*.ini`, `*.env`, `*.csv`, `*.xml`, …) to\n`.gitattributes`. Pass your own patterns to override the defaults:\n\n```bash\nconfdiff install-git-driver \"*.conf\" \"config/**/*.json\"\n```\n\nNow a change that only reorders keys shows *no semantic changes*, while a real\nvalue change shows exactly what moved:\n\n```console\n$ git diff config/app.yaml\nconfdiff config/app.yaml\n~ server.port  8080 => 9090\n```\n\nPrefer to wire it up by hand? It's two lines:\n\n```bash\ngit config diff.confdiff.command 'confdiff --git-diff-driver'\necho '*.yaml diff=confdiff' >> .gitattributes\n```\n\n> `--git-diff-driver` receives git's 7 diff arguments and maps them to the two\n> file versions for you — this is the correct invocation for a git diff driver.\n\n## GitHub Action — semantic config diff on your PRs\n\nSurface the *real* changes in config files right in the PR, instead of a wall of\nreformatted text. The action inspects every changed JSON/YAML/TOML/INI/`.env`/CSV/XML\nfile and posts a single sticky comment showing only the key/value changes — reordered\nkeys, reformatting, comments and quoting are ignored.\n\n```yaml\n# .github/workflows/confdiff.yml\nname: confdiff\non: pull_request\npermissions:\n  contents: read\n  pull-requests: write   # needed to post the comment\njobs:\n  config-diff:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0   # confdiff needs the base commit to compare against\n      - uses: esperanza-volkov/confdiff@v1\n```\n\nA change to `deploy/values.yaml` then shows up as a comment like:\n\n```diff\n~ image      \"nginx:1.25\" => \"nginx:1.26\"\n~ replicas   3 => 5\n+ newFlag    = true\n```\n\n**Inputs** (all optional): `paths` (pathspecs to limit which files are checked),\n`args` (extra confdiff flags, e.g. `--loose --ignore metadata.*`), `redact`\n(`true`/`false`, default `false` — mask secret values as fingerprints so a changed\ncredential is never posted to the PR comment; **recommended** for any repo with\nsecrets-bearing config), `base` (ref to diff against), `comment` (`true`/`false`,\ndefault `true`), `fail-on-diff` (fail the job on any semantic change),\n`github-token`. **Output:** `changed` (`true`/`false`).\n\n```yaml\n      - uses: esperanza-volkov/confdiff@v1\n        with:\n          redact: true          # never leak a changed secret into the PR comment\n```\n\nTo gate merges on config changes instead of commenting:\n\n```yaml\n      - uses: esperanza-volkov/confdiff@v1\n        with:\n          comment: false\n          fail-on-diff: true\n          paths: 'config/** k8s/**'\n```\n\n## Programmatic API\n\n```ts\nimport { compare, diff, parseContent } from \"confdiff\";\n\n// high-level: raw strings, formats auto-detected or forced\nconst changes = compare(rawA, rawB, {\n  formatA: \"json\",\n  formatB: \"yaml\",\n  ignore: [\"metadata.*\"],\n});\n\n// low-level: diff two already-parsed values\nconst d = diff({ a: 1 }, { a: 2 }); // [{ path: [\"a\"], kind: \"change\", ... }]\n```\n\nEach `Change` is `{ path, kind: \"add\"|\"remove\"|\"change\", oldValue?, newValue?, typeChanged? }`.\n\n## MCP server — let your AI agent diff configs\n\nconfdiff ships an [MCP](https://modelcontextprotocol.io) server, so an AI coding\nassistant (Claude Desktop, Cursor, Cline, Windsurf, …) can compare configs by\n**meaning** instead of pasting whole files and eyeballing the noise. It exposes\ntwo tools:\n\n- **`diff_configs`** — diff two config strings the model already has in context.\n- **`diff_config_files`** — read two files from disk by path and diff them.\n\nBoth accept the same options as the CLI (`ignore`, `only`, `arrayKey`,\n`arraySet`, `loose`, `formatA`/`formatB`, `redact`, `redactEntropy`) and return\na compact list of the *real* changes plus structured JSON — key order and\nformatting noise are dropped. Crucially, **`redact: true` masks secret values as\nstable fingerprints**, so plaintext passwords/tokens in a config never enter the\nmodel's context while drift stays visible.\n\nAdd it to any MCP client config:\n\n```jsonc\n{\n  \"mcpServers\": {\n    \"confdiff\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"confdiff-mcp\"]\n    }\n  }\n}\n```\n\nThe server ships as its own package,\n[**`confdiff-mcp`**](https://www.npmjs.com/package/confdiff-mcp), and is listed in\nthe official [MCP Registry](https://registry.modelcontextprotocol.io/) as\n`io.github.esperanza-volkov/confdiff-mcp`. Or install globally with\n`npm i -g confdiff-mcp` and use `\"command\": \"confdiff-mcp\"` with no `args`.\n(It also remains available as a `confdiff-mcp` bin inside the main `confdiff`\npackage via `[\"-y\", \"-p\", \"confdiff\", \"confdiff-mcp\"]`.)\n\nThere's no build step, API key, or environment variable to set — the first run\nfetches the published package via `npx`. Agents that install MCP servers\nautomatically (e.g. Cline) can follow\n[`llms-install.md`](./llms-install.md) for the exact one-step setup.\nThen ask the agent things like *\"did my edit to `prod.yaml` change anything real\nbesides the replica count?\"* or *\"diff these two Helm renders, ignoring\ntimestamps, and don't show me any secret values.\"*\n\n## How it decides two files are equal\n\n1. Parse both sides into a plain data model (objects, arrays, scalars).\n2. Compare recursively, key by key, ignoring object key order.\n3. Report `add` / `remove` / `change`, flagging when a change also changed the\n   value's type.\n\nComments, whitespace, quoting style, key order, and (optionally) array order are\nall considered non-semantic and never reported.\n\n## Questions & feedback\n\nHave a config file that diffs wrong, a format you'd like added, or a way you use\nconfdiff worth sharing? Open a thread in\n**[GitHub Discussions](https://github.com/esperanza-volkov/confdiff/discussions)**\n(Q&A / Ideas / Show and tell) — real-world files that confuse it are the single\nmost useful thing you can share. Bugs are best filed as\n[issues](https://github.com/esperanza-volkov/confdiff/issues).\n\n## Contributing\n\nIssues and pull requests are welcome. Run the test suite with:\n\n```bash\nnpm install\nnpm test\nnpm run build\n```\n\nSee [CONTRIBUTING.md](./CONTRIBUTING.md) for the full guide (including how to add\na new format), [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md), and\n[CHANGELOG.md](./CHANGELOG.md) for release notes.\n\n## License\n\n[MIT](./LICENSE) © Esperanza Volkov\n",
  "bytes": 27704,
  "sha": "2de9e8005f5930cd9b943bbd9830a125add314f742143a3fbb177b3bdc001c98",
  "repo_slug": "esperanza-volkov/confdiff",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_esperanza_volkov_confdiff_mcp_7830da9a/readme"
}