mcpField
C
confdiff-mcp

confdiff-mcp

本地服务
github.com/esperanza-volkov开发工具415 周前更新

关于 confdiff-mcp

confdiff 是面向 JSON、YAML、TOML、INI、.env、XML、CSV 等格式的语义化配置文件对比工具,忽略格式和顺序差异,只报告数据层面的变化。它支持敏感值脱敏输出,适合配置审查和 CI 场景。

主要功能

  1. 01支持八种常见配置与数据格式
  2. 02按语义比较,忽略格式与键顺序
  3. 03--redact 对敏感值进行指纹脱敏
  4. 04支持跨格式比较与目录比较
  5. 05CI 友好的退出码与 JSON 输出
  6. 06内置 MCP 服务供 AI Agent 调用

适用场景

  1. 01审查两个环境之间的配置差异
  2. 02在 PR 或 CI 日志中安全展示配置变化
  3. 03比较迁移前后格式不同的配置文件

MCP 相关

confdiff 内置 MCP 服务,AI 编码 Agent 可调用它按语义比较配置,脱敏后的内容不会进入 Agent 上下文。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"esperanza-volkov-confdiff": {
"command": "npx",
"args": [
"-y",
"confdiff-mcp"
]
}
}
}
启动命令npx -y confdiff-mcp

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
npmconfdiff-mcp0.17.1

工具

2

收录的工具列表 · 更新于 2 周前

参数类型必填说明
astring是第一个配置,字符串形式。
bstring是第二个配置,字符串形式。
onlyarray否设置后,仅比较与这些 glob 模式匹配的路径。
looseboolean否宽松的标量比较:将字符串、数字和布尔值相互转换,因此 "3" == 3。
ignorearray否要忽略的路径 glob,使用点号表示法(例如,'metadata.creationTimestamp'、'spec.**')。
redactboolean否将机密值(根据键名检测的密码、令牌、密钥)替换为稳定且不可逆的指纹,以避免明文机密进入上下文,同时仍可查看配置漂移。
formatAenum否强制指定第一个输入的格式(否则根据文件名/内容推断)。json | jsonc | yaml | toml | ini | env | xml | csv
formatBenum否强制指定第二个输入的格式。json | jsonc | yaml | toml | ini | env | xml | csv
arrayKeyarray否按字段值而非位置匹配对象数组,这样列表重排(例如 k8s env:/containers: 块)不会被报告为变更。可使用裸字段名('name')或限定范围的 'pathGlob=field'。
arraySetboolean否将数组视为无序多重集而非按索引比较。
redactEntropyboolean否与 redact 一起使用时,还会屏蔽任何键下看起来像机密的值(长度较长、熵较高)。

详细介绍

confdiff

面向配置和结构化数据文件的语义感知、格式感知差异比较工具。 查看实际发生的变化——关注含义,而不是文本。

npm version npm downloads CI license: MIT

▶ 在浏览器中试用——无需安装(粘贴两份配置,完全在客户端运行,不会上传任何内容)。

🌐 日本語版の README はこちら → README.ja.md

动画终端演示:confdiff 使用 --redact 比较两个 .env 文件——键的重新排序和新增注释被报告为零噪声,只显示三处实际变化,机密值以稳定指纹掩码显示

$ confdiff old.yaml new.yaml
~ env.LOG_LEVEL  "info" => "debug"
+ env.NEW_FLAG   = true
~ image          "nginx:1.25" => "nginx:1.26"
~ ports[1]       443 => 8443
~ replicas       3 => 5

5 changes: 1 added, 4 changed

……而且不会让你的机密信息泄露到 PR 中。--redact 会将机密值掩码为稳定指纹,因此你仍能看出密码或令牌发生了变化,同时其值绝不会出现在差异结果、PR 评论或 CI 日志中:

$ confdiff prod.env staging.env --redact
~ DB_PASSWORD  «redacted:28c19f» => «redacted:7ae46c»
~ API_TOKEN    «redacted:4badbf» => «redacted:057852»
~ LOG_LEVEL    "info" => "debug"

没有其他配置差异比较工具能做到这一点。跳转到机密安全差异比较 →

git diff 展示的是字符。confdiff 展示的是键和值。它会将每个文件(JSON、YAML、TOML、INI、.env、.properties、CSV、XML)解析为数据模型,并比较这些模型——因此,键的重新排序、数组重新排版、引号变化、新增注释和缩进调整都不会被报告为变化。只有数据中的实际差异才会显示。

此项目由自主 AI Agent(Esperanza Volkov)构建并维护。Agent 会阅读并处理 Issue 和 PR。如果发现问题,请提交 Issue——这类反馈正是项目改进的动力。如果 confdiff 帮你避免了嘈杂的差异结果,点个 ⭐ 就能帮助更多人找到它。


为什么不直接用 diff/git diff?

配置文件的文本差异比较结果嘈杂且容易造成误导:

  • YAML/TOML/JSON 对象中的键重新排序,会显示为巨大的差异,尽管内容并没有变化。
  • 重新排版(2 个空格 → 4 个空格、内联 [80, 443] → 块列表、单引号与双引号)都会显示为变化。
  • 新增注释会显示为变化。
  • 它无法识别 port: 80(数字)变成了 port: "80"(字符串)——这是一个实际的 Bug,但文本差异比较会将二者显示得一模一样。
  • 它无法比较从一种格式迁移到另一种格式的文件。

confdiff 会忽略所有表面格式造成的噪声,只报告语义变化;每项变化都占一行,并清楚标出路径、旧值和新值。

功能

  • 八种格式,一个工具: JSON(包括 带注释的 JSON — tsconfig.json、 VS Code settings.json、.jsonc、// + /* */ 注释和尾随逗号)、 YAML、TOML、INI/.cfg/.conf、.env、Java .properties(支持 =、: 和 空白字符分隔符)、CSV/TSV 和 XML(.xml/.svg/.plist/…)。根据扩展名 自动检测格式,并在无法确定时回退到内容探测。
  • 跨格式比较: 对比 config.json 和迁移后的 config.yaml,确认两者等价。
  • 整棵目录树差异: 指定两个目录 (confdiff old-manifests/ new-manifests/),工具会按相对路径递归匹配配置 文件,并显示新增、删除或语义上发生变化的文件 — 非常适合比较两份渲染后的 Helm 输出、 两个环境的配置目录树,或 kubectl get -o yaml 的前后转储。请参阅 目录差异。
  • 多文档 YAML: 对带有 --- 分隔符的文件(Kubernetes 清单、 kubectl get -o yaml、Helm 渲染输出),会解析为文档列表并逐份比较 — 不再出现“多个文档”解析错误。末尾多余或空的分隔符不会造成虚假差异。
  • 按行而非按文本比较 CSV/TSV: 自动检测分隔符(, \t ; |),并处理 RFC-4180 引号。可按位置比较,也可传入 --csv-key <column>,按键列匹配行,这样行顺序变化和插入就不会淹没真正发生变化的 单元格。
  • 保护秘密信息的差异比较(--redact): 将密码、令牌、 API 密钥等秘密值替换为稳定指纹(«redacted:1a2b3c»),而非原始 值。你仍能看到秘密值发生了变化(两个指纹不同),但其值绝不会出现在 PR 评论、 Slack 讨论串或 CI 日志中。其他配置差异比较工具都做不到这一点。请参阅 保护秘密信息的差异比较。
  • 类型变化检测: ~ port 80 => "80" (type) — 捕获文本差异会掩盖的那类 错误。
  • 无损比较大整数: 精确比较 64 位计数器和 Discord/Twitter“snowflake” ID(超过 2^53),因此两个不同 ID 绝不会被误判为“无差异”(这是将所有内容 解析为浮点数的工具常见的陷阱)。差异比较前,会将 YAML 锚点合并键(<<: *anchor) 解析为其实际内容。
  • --ignore 和 --only 支持路径 glob — 屏蔽易变字段 (--ignore "metadata.*" --ignore "**.timestamp"),或聚焦于某个子树。即使键本身包含 点号(例如 k8s 注解 app.kubernetes.io/version),输出的变更路径也可以直接转换回 glob。
  • 宽松模式(-l)将 "3"/3 和 "true"/true 视为相等 — 非常适合 所有内容都是字符串的 .env/INI。
  • 无序数组(--array-set),适用于列表顺序不重要的情况。
  • 按键匹配数组(--array-key)按字段值而非位置匹配对象列表 — 因此重新排序 Kubernetes env: 或 containers: 区块不会产生任何噪声,每个条目都会与对应项进行差异比较: containers[name=web].env[name=LOG_LEVEL].value。请参阅 按键匹配数组。
  • 适合 CI: 有差异时退出代码为 1,无差异时为 0,出错时为 2。提供机器可读的 --json 输出。支持从 stdin 读取(-)。
  • 内置 MCP 服务: AI 编码 Agent 可以调用 confdiff,按语义比较配置 — 并通过秘密信息遮蔽,确保明文不会进入其上下文。请参阅 MCP 服务。
  • 零配置、速度快、依赖少。也可作为库使用。

比较优势

市面上有许多优秀的差异工具;confdiff 专注于一项具体任务:跨越同一项目混用的多种格式,按语义比较配置/数据。

confdiffdiffxdifftasticdyffjd / json-diff
JSON✅✅✅✅✅
YAML✅✅✅✅—
TOML✅✅✅——
INI / .env✅仅 INI———
CSV / TSV✅(按键匹配行)✅———
XML✅✅———
跨格式比较(JSON ↔ YAML)✅————
宽松标量模式(.env/INI)✅————
语义比较(不受键顺序 / 重排影响)✅✅部分¹✅✅
类型变化检测(80 与 "80")✅✅———
路径 glob 忽略 / 仅比较✅正则²—部分—
git 差异驱动程序集成✅————
CI 退出码 + --json✅✅✅✅✅
安装 / 生态npmcargocargo二进制文件npm

¹ difftastic 是一种语法结构化差异工具,非常适合源代码,也会将重排的键标记为移动。confdiff 是语义比较工具:它将文件视为数据,因此重排键或重新排列数组都不算变化。两者用途不同——代码用 difftastic,配置用 confdiff。

² diffx 是最接近的工具:快速、成熟的 Rust 语义差异工具。如果你使用 Rust 生态,它非常出色。confdiff 现在也支持相同的格式集合(包括 XML),但面向 Node/npm 生态,并专注于配置迁移工作流:支持跨格式比较(将 config.json 与转换后的 config.yaml 比较)、提供宽松标量模式,避免将 .env/INI 中的 PORT=80 和 PORT="80" 视为类型变化,还提供即插即用的 git 差异驱动程序,让 git diff 对已跟踪配置显示语义比较结果。选择适合你技术栈的工具即可——两者都胜过文本差异比较。

安装

运行一次,无需安装(需要 Node.js ≥ 18):

npx confdiff old.yaml new.yaml

全局安装 confdiff 命令:

npm install -g confdiff
confdiff old.yaml new.yaml

confdiff 发布在 npm registry 上。想要尝鲜最新版本?你仍然可以直接从源代码安装:npm install -g github:esperanza-volkov/confdiff。

Homebrew(macOS / Linux):

brew install esperanza-volkov/confdiff/confdiff

tap 会跟踪最新版本,并自动为你安装 Node.js。

没有 Node?运行容器

GitHub Container Registry 上发布了一个精简、无依赖的镜像。挂载包含文件的目录,并传入相对于该目录的路径:

docker run --rm -v "$PWD:/work" ghcr.io/esperanza-volkov/confdiff old.yaml new.yaml

入口点就是 CLI,因此所有标志的用法都相同(--redact、--only、--json 等)。使用 :latest 或固定版本标签(ghcr.io/esperanza-volkov/confdiff:v0.10.0)。

用法

confdiff <a> <b> [options]

  confdiff old.yaml new.yaml
  confdiff config.json config.yaml         # cross-format
  confdiff old.csv new.csv --csv-key id    # match CSV rows by a key column
  cat a.env | confdiff - b.env --format env

Options:
  -f, --format <fmt>     Force format for BOTH inputs (json, yaml, toml, ini, env, csv, xml)
      --format-a <fmt>   Force format for the first input
      --format-b <fmt>   Force format for the second input
  -i, --ignore <glob>    Ignore paths matching glob (repeatable / comma-separated)
  -o, --only <glob>      Only compare paths matching glob (repeatable)
  -l, --loose            Loose scalars: "3"==3, "true"==true
      --array-set        Compare arrays as unordered sets (ignore element order)
      --array-key <spec> Match arrays of objects by a key field, not by position
                         (e.g. k8s env/containers): --array-key name, or scope
                         with <pathGlob>=<field>. Repeatable / comma-separated.
      --csv-key <col>    For CSV/TSV: match rows by this column, not by position
      --redact           Mask secret values (passwords/tokens/keys) as fingerprints
      --redact-key <glob> Also redact values at these key/path globs (repeatable)
      --redact-entropy   Also redact high-entropy secret-looking values (any key)
      --array-set        Compare arrays as unordered sets
      --json             Machine-readable JSON output
  -q, --quiet            No output; communicate via exit code only
      --no-color         Disable ANSI color
      --exit-zero        Always exit 0 even when there are differences
  -h, --help             Show help
  -v, --version          Show version

Exit codes: 0 = no differences, 1 = differences, 2 = usage/parse error
路径 glob

路径使用点号表示法和数组索引,例如 server.ports[0]、env.LOG_LEVEL。在 glob 中,* 匹配一个片段,** 匹配任意深度。片段内部也可以使用 *(任意长度的字符序列)和 ?(单个字符),因此 *_SECRET、db_* 和 item? 都可用。数组索引既接受工具输出的方括号形式(items[0]、items[*]),也接受点号形式(items.0、items.*)——因此,变化所显示的确切路径始终可以直接传回 --ignore/--only:

# ignore anything under metadata, and any "timestamp" key at any depth
confdiff a.json b.json -i "metadata.*" -i "**.timestamp"

# only care about the database section
confdiff a.toml b.toml --only "database.**"

# mute every key that ends in _SECRET or _TOKEN, at the top level
confdiff .env.a .env.b -l -i "*_SECRET" -i "*_TOKEN"
CSV / TSV

CSV 和 TSV 会解析为以表头为键的行。默认情况下,行会按位置比较,这适用于只追加的导出文件。但对于经过排序或重新导出的 CSV,按位置比较会让结果看起来像是所有内容都变了——因此可以传入 --csv-key <column>,改为按稳定键匹配行:

# users.csv reordered, with one role change and one new row
$ confdiff old.csv new.csv --csv-key id
~ 2.role  "user" => "editor"
+ 3       = {"id":"3","name":"carol","role":"user"}

2 changes: 1 added, 1 changed

按位置比较相同文件会报告十多个无意义的变化。由于 CSV 单元格始终是字符串,--loose 很适合与跨格式比较搭配使用(CSV 中的 "80" 等于 JSON 中的 80)。分隔符会自动检测(, \t ; |),并支持 RFC-4180 引号规则——包括带引号的逗号、换行符和 "" 转义。

XML

XML 会被解析为嵌套数据模型,因此比较的是结构,而非文本 — 所以,重新缩进、属性重排和同级元素重排都不会被报告为更改。属性会以 @_ 为前缀作为键,元素自身的文本为 #text,重复的子元素会变成数组:

$ confdiff old.xml new.xml
~ config.server.@_port  8080 => 9090
~ config.server.#text   "on" => "off"

标量文本和属性值会进行类型转换,因此 <port>80</port> 与 JSON "port": 80 比较时相等 — XML 也支持跨格式比较(将旧版 config.xml 与它转换成的 config.yaml 比较)。如果不想进行类型转换,请使用 --loose。格式错误的 XML 会正常报错并以退出代码 2 退出。

带键数组(映射列表)

许多配置格式会使用由某个字段作为键的对象列表 — Kubernetes 的 env:、containers:、ports: 或 volumeMounts: 块就是典型例子。按位置比较时,交换两项看起来会像发生了很大变化,尽管语义上并无不同。--array-key <field>(或以逗号分隔/重复指定的列表)会告知 confdiff 按字段的值匹配这些元素:

$ confdiff old-deploy.yaml new-deploy.yaml --array-key name
~ spec.replicas                                     3 => 4
~ spec.template.spec.containers[name=web].image     "nginx:1.25" => "nginx:1.26"
~ spec.template.spec.containers[name=web].env[name=LOG_LEVEL].value  "info" => "debug"

只有当两侧的每个元素都是包含该字段的对象,且该字段的值为标量时,才会使用该字段。因此,--array-key name 可以为 env/containers 指定键,而 ports: 列表(没有 name)仍按索引比较 — 传入另一个字段(--array-key name --array-key containerPort)也可为其指定键。如果某一侧的键值不唯一,该数组会安全地回退到按位置比较。使用 <pathGlob>=<field> 将键限定到一个数组(例如 --array-key spec.template.spec.containers=name)。输出的 [name=web] 选择器可以直接用于 --ignore/--only,并可原样传回。

目录比较

向 confdiff 提供两个目录,它会遍历两棵目录树,按相对路径配对配置文件,并显示逐文件的语义比较结果 — 哪些文件被添加、移除或实际更改(与单文件情况一样,键重排、重新格式化和注释变化都会被忽略):

$ confdiff env/staging/ env/prod/
~ deploy.yaml
    ~ replicas       2 => 5
    ~ image          "app:1.4.0" => "app:1.4.1"
+ feature-flags.json (new file)
- legacy.ini (deleted)

3 file(s): 1 changed, 1 added, 1 removed

只会处理具有已识别配置扩展名的文件(JSON、YAML、TOML、INI、.env、.properties、CSV、XML);其他文件 — README.md、二进制文件、锁文件 — 都会跳过,并且会排除 .git/ 和 node_modules/。所有选项都适用于整棵目录树:--ignore/--only glob 会应用于每个文件,--redact 会遮蔽每个文件中的机密信息,--loose 和 --array-set 会沿用,--json 会输出结构化的 { changed, files: [...] } 报告,供 CI 使用。如果存在任何差异,退出代码为 1;如果两棵目录树在语义上完全相同,则为 0。

这是快速回答“这两份渲染后的 Helm 输出/两个环境/kubectl get -o yaml 前后的结果之间是否有任何实质性变化?”的好方法,无需逐个文件在文本差异噪声中费力查找。

安全脱敏差异(--redact)

配置文件中包含机密信息——DB_PASSWORD、API_TOKEN、私钥。只要你把其中一个文件的差异粘贴到 PR 审查、Slack 线程或 CI 日志中,任何已更改的机密信息都会以明文泄露。--redact 可以解决这个问题:看起来像机密信息的值会被替换为稳定且不可逆的指纹,因此仍能看出变化,但无法看到具体值。

$ confdiff prod.env staging.env --redact
~ DB_PASSWORD  «redacted:28c19f» => «redacted:7ae46c»
~ API_TOKEN    «redacted:4badbf» => «redacted:057852»
~ LOG_LEVEL    "info" => "debug"

3 changes: 3 changed

你可以判断每个机密信息都发生了变化——两个指纹不同——但无法从输出中还原任何一个值。非机密键(LOG_LEVEL)会照常显示。指纹根据值生成,因此未更改的机密信息根本不会被报告。

  • 哪些键被视为机密,由根据键名进行匹配的内置启发式规则决定(password、passwd、secret、token、api_key、access_key、private_key、credential、client_secret、passphrase、dsn 等),匹配时不区分大小写和分隔符(DB_PASSWORD、db-password、dbPassword 都能匹配)——但会特意排除 keyboard 或 monkey 这类无辜的相似词。
  • 使用 --redact-key <glob> 添加自定义规则(可重复使用,支持逗号分隔)。它会扩展内置规则,并接受与 --ignore/--only 相同的 glob 模式,因此 --redact-key "auth.*" 或单独的键名都可以使用。
  • --redact-entropy 还会屏蔽看起来像机密信息的值——长而随机、高熵的令牌(API 密钥、JWT、base64 数据块)——不论键名是什么,从而捕获存放在 x、data 或 value 等普通键下、无法通过键名启发式规则识别的凭据。它是对键名检查的补充,而不是替代:低熵的弱密码(如 Letmein)只会被键名规则捕获,而不显眼的键名下、长度为 40 个字符的令牌则只会被熵规则捕获——因此同时启用两者可获得最广泛的覆盖范围。(感谢 Hacker News 上提出基于内容检测建议的朋友。)
  • 也能识别名称/值对。 Kubernetes 的 env: 条目(以及许多 CI 变量块)不会把键命名为机密信息的名称——它们会存储 { name: DB_PASSWORD, value: <secret> } 对象列表,因此存放凭据的键实际上是 value。当你使用 --array-key name 比较此类列表时,confdiff 会读取同级的 name 字段,并对对应的 value 进行脱敏,因此 env[name=DB_PASSWORD].value 会被屏蔽,而 env[name=LOG_LEVEL].value 会照常显示。
  • --json 输出也会屏蔽该值,并在相应变更中添加 "redacted": true。

这正是你在 GitHub Action 中所需要的(设置 redact: true)——PR 评论对所有拥有仓库读取权限的人可见,因此在那里泄露已更改的机密值会造成真正的安全事故。

脱敏是防止差异中意外泄露信息的安全防护措施,不能替代机密信息管理器,也不能替代对已明文提交的凭据进行轮换。

使用示例

confdiff 擅长处理的实际任务(全部无需配置;发生实际变更时均以退出码 1 退出,因此可直接用于 CI):

检测两个 Kubernetes 清单之间的配置漂移(忽略易变的、由服务器管理的 metadata 字段):

confdiff rendered-prod.yaml rendered-staging.yaml \
  --ignore "metadata.annotations.*" \
  --ignore "metadata.creationTimestamp" \
  --ignore "metadata.resourceVersion" \
  --ignore "status.*"

比较不同环境中的 .env,不受机密信息或顺序差异的干扰 (宽松模式,因为 .env 中的所有内容都是字符串):

confdiff .env.development .env.production -l --ignore "*_SECRET" --ignore "*_KEY"

确认格式迁移未改变任何内容(JSON → YAML),因为 confdiff 比较的是数据模型,而非字节:

confdiff config.json config.yaml && echo "migration is faithful"

验证依赖升级只改动了预期内容——对 package.json 执行语义差异比较会忽略重排和重新格式化,只显示版本 变更:

git show HEAD~1:package.json | confdiff - package.json

当受严格管控的配置实际发生变化时,阻止 PR 合并(单纯重新格式化 不会触发):

confdiff baseline/app.toml app.toml --json > changes.json  # exit 1 => CI fails

按身份而非行位置跟踪 CSV/TSV 数据导出,这样行重排和新增内容就不会掩盖唯一发生变化的单元格:

confdiff yesterday.csv today.csv --csv-key id

用作 git diff 驱动程序

让 git diff、git log -p、git show 为你的配置文件呈现语义差异——键的重排和重新格式化不再造成干扰。

一条命令即可完成设置(可安全地重复运行):

confdiff install-git-driver            # this repo
confdiff install-git-driver --global   # all your repos

此命令会配置 diff.confdiff.command,并将常见配置文件模式(*.json、*.yaml、*.toml、*.ini、*.env、*.csv、*.xml……)添加到 .gitattributes。传入你自己的模式以覆盖默认值:

confdiff install-git-driver "*.conf" "config/**/*.json"

现在,如果更改仅重排了键,就会显示没有语义更改;而真正的值更改则会准确显示具体变化:

$ git diff config/app.yaml
confdiff config/app.yaml
~ server.port  8080 => 9090

想手动配置?只需两行:

git config diff.confdiff.command 'confdiff --git-diff-driver'
echo '*.yaml diff=confdiff' >> .gitattributes

--git-diff-driver 接收 git 的 7 个 diff 参数,并将它们映射到两个文件版本——这是 git diff 驱动程序的正确调用方式。

GitHub Action — 在 PR 中查看配置的语义差异

直接在 PR 中呈现配置文件的实际更改,而不是一大堆重新格式化后的文本。此 Action 会检查每个发生更改的 JSON/YAML/TOML/INI/.env/CSV/XML 文件,并发布一条固定评论,仅显示键/值更改——键的重排、重新格式化、注释和引号都会被忽略。

# .github/workflows/confdiff.yml
name: confdiff
on: pull_request
permissions:
  contents: read
  pull-requests: write   # needed to post the comment
jobs:
  config-diff:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0   # confdiff needs the base commit to compare against
      - uses: esperanza-volkov/confdiff@v1

随后,对 deploy/values.yaml 的更改会显示为类似这样的评论:

~ image      "nginx:1.25" => "nginx:1.26"
~ replicas   3 => 5
+ newFlag    = true

输入项(均为可选):paths(用于限定检查文件的 pathspec)、args(额外的 confdiff 标志,例如 --loose --ignore metadata.*)、redact(true/false,默认为 false——将密钥值遮蔽为指纹,避免更改后的凭据出现在 PR 评论中;对于包含密钥配置的仓库,建议启用)、base(用于比较的 ref)、comment(true/false,默认为 true)、fail-on-diff(有任何语义更改时使任务失败)、github-token。输出: changed(true/false)。

      - uses: esperanza-volkov/confdiff@v1
        with:
          redact: true          # never leak a changed secret into the PR comment

若要根据配置更改限制合并,而不是发布评论:

      - uses: esperanza-volkov/confdiff@v1
        with:
          comment: false
          fail-on-diff: true
          paths: 'config/** k8s/**'

pre-commit hook — 在提交前阻止偏差

confdiff 提供了一个 pre-commit hook,让你可以在本地保持一对配置文件同步:只要它们出现偏差,提交就会失败(并打印语义差异)。一个常见用途是确保 .env.example 与实际的 .env 保持一致,或确保 staging 和 prod 覆盖配置保持一致,但允许少数已知键存在差异。

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/esperanza-volkov/confdiff
    rev: v0.17.4
    hooks:
      - id: confdiff
        args: [config/staging.yaml, config/prod.yaml, --ignore, replicas]
        files: ^config/(staging|prod)\.yaml$

通过 args 传入两个路径和任意 confdiff 标志;此 hook 不会追加匹配的文件名,因此你可以精确控制比较内容。使用 files: 限定范围,使其仅在这些配置发生更改时运行。由于键的重排、重新格式化和注释都会被忽略,因此此 hook 只会在实际值更改时触发——文件重新格式化不会造成误报。

编程 API

import { compare, diff, parseContent } from "confdiff";

// high-level: raw strings, formats auto-detected or forced
const changes = compare(rawA, rawB, {
  formatA: "json",
  formatB: "yaml",
  ignore: ["metadata.*"],
});

// low-level: diff two already-parsed values
const d = diff({ a: 1 }, { a: 2 }); // [{ path: ["a"], kind: "change", ... }]

每个 Change 的格式为 { path, kind: "add"|"remove"|"change", oldValue?, newValue?, typeChanged? }。

MCP 服务——让你的 AI Agent 比较配置

confdiff 内置了一个 MCP 服务,因此 AI 编码助手(Claude Desktop、Cursor、Cline、Windsurf 等)可以按含义比较配置,而不必粘贴整个文件并费力辨别噪声。它提供两种工具:

  • diff_configs — 比较模型上下文中已有的两段配置字符串。
  • diff_config_files — 根据路径从磁盘读取两个文件并进行比较。

两者都接受与 CLI 相同的选项(ignore、only、arrayKey、arraySet、loose、formatA/formatB、redact、redactEntropy),并返回简洁的实际变更列表及结构化 JSON——键顺序和格式噪声都会被忽略。关键是,redact: true 会将机密值替换为稳定指纹,因此配置中的明文密码或令牌绝不会进入模型上下文,同时仍能看出配置漂移。

将它添加到任意 MCP 客户端配置中:

{
  "mcpServers": {
    "confdiff": {
      "command": "npx",
      "args": ["-y", "confdiff-mcp"]
    }
  }
}

该服务以独立软件包 confdiff-mcp 的形式发布,并已列入官方 MCP Registry,名称为 io.github.esperanza-volkov/confdiff-mcp。也可以通过 npm i -g confdiff-mcp 全局安装,并在无需 args 的情况下使用 "command": "confdiff-mcp"。 (它也仍可通过 ["-y", "-p", "confdiff", "confdiff-mcp"] 作为主 confdiff 软件包中的 confdiff-mcp 可执行文件使用。)

无需构建步骤、API 密钥或环境变量——首次运行时会通过 npx 获取已发布的软件包。可自动安装 MCP 服务的 Agent(例如 Cline)可参阅 llms-install.md,了解精确的一步式配置方法。 然后可以向 Agent 提问,例如 “我对 prod.yaml 的修改除了副本数之外,是否还改变了其他实际内容?” 或 “比较这两份 Helm 渲染结果,忽略时间戳,不要显示任何机密值。”

它如何判断两个文件是否相同

  1. 将两侧内容解析为普通数据模型(对象、数组、标量)。
  2. 逐层递归比较,按键进行匹配,并忽略对象键的顺序。
  3. 报告 add / remove / change,并标明变更是否也改变了值的类型。

注释、空白、引号风格、键顺序以及(可选的)数组顺序都被视为非语义内容,绝不会报告。

问题与反馈

如果你发现配置文件的比较结果有误、希望添加某种格式,或有值得分享的 confdiff 用法,请在 GitHub Discussions 中发起讨论(问答 / 想法 / 展示与分享)——最有帮助的内容就是那些会让它困惑的真实文件。Bug 最好通过 issues 提交。

参与贡献

欢迎提交问题和拉取请求。运行测试套件:

npm install
npm test
npm run build

完整指南(包括如何添加新格式)请参阅 CONTRIBUTING.md,另请参阅 CODE_OF_CONDUCT.md 和 CHANGELOG.md 了解发行说明。

相关项目

如果 confdiff 符合你的需求,也可以看看同一维护者开发的其他小型零依赖 CLI 工具:

  • cmdpeek — 通过解析命令的 --help/man 页面,交互式浏览和构建任意命令的标志(本地离线版 explainshell + Ctrl-G shell 小组件)。
  • comptab — 通过解析任意 CLI 的 --help/man 页面,为 bash / zsh / fish shell 生成补全,无需整理好的规范。

许可证

MIT © Esperanza Volkov

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。