Metadata-Version: 2.4
Name: cfgdrift
Version: 0.20.1
Summary: Semantic-level configuration drift detection system (JSON/TOML/INI parsed in C or pure Python, YAML via PyYAML)
Author-email: Zhiyu Dong <dongzhiyu0402@gmail.com>
License: MIT
Project-URL: Homepage, https://github.com/Dongzhiyu0402/cfgdrift
Project-URL: Repository, https://github.com/Dongzhiyu0402/cfgdrift
Project-URL: Documentation, https://dongzhiyu0402.github.io/cfgdrift/
Keywords: config,drift,detection,semantic,diff
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: C
Classifier: Operating System :: OS Independent
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: click>=8.1
Requires-Dist: PyYAML>=6.0
Requires-Dist: tomli>=2.0; python_version < "3.11"
Provides-Extra: web
Requires-Dist: fastapi>=0.110; extra == "web"
Requires-Dist: uvicorn>=0.27; extra == "web"
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: fastapi>=0.110; extra == "dev"
Requires-Dist: uvicorn>=0.27; extra == "dev"
Requires-Dist: httpx>=0.24; extra == "dev"
Requires-Dist: httpx2>=2.0; python_version >= "3.10" and extra == "dev"
Dynamic: license-file

# cfgdrift — 语义级配置漂移检测系统

**简体中文** | [English](./README.en.md)

[![CI](https://img.shields.io/github/actions/workflow/status/Dongzhiyu0402/cfgdrift/ci.yml?branch=main&label=CI)](https://github.com/Dongzhiyu0402/cfgdrift/actions/workflows/ci.yml)
[![PyPI version](https://img.shields.io/pypi/v/cfgdrift.svg)](https://pypi.org/project/cfgdrift/)
[![PyPI downloads](https://img.shields.io/pypi/dm/cfgdrift.svg)](https://pypi.org/project/cfgdrift/)
[![License](https://img.shields.io/github/license/Dongzhiyu0402/cfgdrift.svg)](https://github.com/Dongzhiyu0402/cfgdrift/blob/main/LICENSE)
[![Python versions](https://img.shields.io/pypi/pyversions/cfgdrift.svg)](https://pypi.org/project/cfgdrift/)
[![Stars](https://img.shields.io/github/stars/Dongzhiyu0402/cfgdrift.svg?style=social&label=Stars)](https://github.com/Dongzhiyu0402/cfgdrift)

`cfgdrift` 是语义级（semantic-level）配置漂移检测工具：解析 JSON / YAML / TOML / INI / XML / Java properties / .env / nginx 为结构化语义树，忽略注释 / 缩进 / 键序等格式噪音，只报告配置「含义」的真实变化。适合配置变更审计、安全合规检查、CI 门禁与日常运维巡检。

## ✨ 亮点

- **精准检测**：检出新增 / 删除 / 修改 / 类型变化 / **重排**五类漂移，按 CRITICAL / WARN / INFO 分级，误报趋近于零
- **闭环可操作**：采集 → 解析 → 基线 → 比对 → 报告一条命令打通；基线版本化 + 回滚，SQLite 历史可追溯
- **无人值守**：daemon 周期扫描，检出漂移触发 webhook / 邮件 / 脚本 / Slack / Teams / PagerDuty 六通道告警（规则级重试可配），支持开机自启（systemd / launchd / schtasks）
- **工程友好**：退出码 0/1/2 契约可直接接入 CI/CD；JSON / 单文件离线 HTML 报告；本地 Web 仪表盘
- **可扩展**：内置 **8 种格式**开箱即用（JSON/YAML/TOML/INI + v0.18.0 的 XML/properties/.env/nginx），插件化解析器接口（entry point `cfgdrift.parsers` + 装饰器注册）支持任意自定义格式
- **随处可装**：C 核心解析 + 纯 Python 兜底，任意 Python 3.8+ 免编译器安装，跨平台

## 特性速览

| 能力 | 一句话说明 | 详见 |
|------|-----------|------|
| 语义 diff + 严重度 | 五类漂移 × CRITICAL/WARN/INFO | [语义 diff 与严重度](#语义-diff-与严重度) |
| 一致性约束 | 五类约束叠加 diff，升级 + 关联判定 | [一致性约束](#一致性约束与约束挖掘) |
| daemon + 告警 | 周期扫描、六通道、重试 / 静默 / 开机自启 | [daemon 与告警](#daemon-与告警) |
| 自愈闭环 | remediate 键级回滚 + 修复建议 hint | [自愈闭环](#自愈闭环remediate--hint) |
| Web 仪表盘 | 10 视图：时间线 / 环境对比 / 自愈审计… | [Web 仪表盘](#web-仪表盘10-视图) |
| corpus + kappa | git 历史挖掘 + 双人标注一致性 | [corpus 基准语料 + kappa](#corpus-基准语料--kappa) |
| 敏感脱敏 | 13 类敏感键五出口打码 | [敏感值脱敏](#敏感值脱敏) |
| 插件 | `--format` 自定义解析格式 | [自定义解析器插件](#自定义解析器插件) |
| 云/部署态采集 | `--source k8s` 采集 K8s ConfigMap / Secret（v0.14.0 / v0.19.0） | [云/部署态采集](#云部署态采集v0140) |
| 原生告警通道 | Slack / Teams / PagerDuty 原生通道（v0.15.0） | [原生告警通道](#原生告警通道v0150) |
| 列表重排检测 | LCS 序列匹配，纯重排报 INFO `reordered` 而非多处误报（v0.16.0） | [列表重排检测](#列表重排检测v0160) |
| Web 认证 | 可选 Bearer token 保护全部 `/api/*`，未配置时零噪音（v0.17.0） | [Web 认证](#web-认证v0170) |
| 内置解析器扩充 | 内置 8 格式开箱即用（+ XML / properties / .env / nginx，v0.18.0） | [内置解析器与自定义插件](#内置解析器与自定义插件) |
| 小白易用性 | `diff OLD NEW` 双文件 / parse error 方言引导 / 基线保存确认 / 常见误区文档（v0.20.0） | [常见误区](#常见误区v0200-小白必读) |
| 双 wheel / C 加速 | 纯 Python 兜底 + CPython3.13 加速 | [安装](#安装) |

## 安装

```bash
pip install cfgdrift            # 通用安装（pip 自动选件）
pip install "cfgdrift[web]"     # 含 Web 仪表盘
pip install "cfgdrift[dev]"     # 含测试依赖
```

- Python 3.8+ 免编译器；C 扩展为可选加速器，未编译或安装失败自动降级纯 Python。
- 双 wheel 模型 / 本地构建配方 / 环境变量表 / 双模式一致性差异：见 [安装文档](./docs-site/installation.html)。

## 快速上手

```bash
cfgdrift init
cfgdrift scan ./config --save-as-baseline prod
# …修改配置…
cfgdrift diff ./config --baseline prod          # 退出码 1 = 有漂移
cfgdrift serve                                   # 打开 http://127.0.0.1:8080
```

## 常见误区（v0.20.0 小白必读）

1. **基线按路径匹配，不按内容**——`scan`/`diff --baseline` 是「当前快照 vs 基线快照」按文件路径（relpath）求交做键级对比。改内容又改文件名 = 旧路径消失 + 新路径出现 → 报告「文件删除 + 新增」，不是「内容修改」。想要内容级对比，直接用双文件：`cfgdrift diff old.conf new.conf`。
2. **`.conf` 默认按 ini 解析**——`.conf`/`.cfg` 扩展名默认映射 ini 方言。HOCON/nginx 风格内容（`server { port = 8080 }`）按 ini 会报 `parse error at line ...`；此时会额外提示 `hint: ... --format nginx 或 --format toml`，按提示换格式即可。
3. **`diff` 有两种用法**——单文件 vs 基线：`cfgdrift diff PATH --baseline NAME`（老用法）；双文件直接对比：`cfgdrift diff OLD NEW`（OLD 为基准、NEW 为目标，输出同一套键级语义 diff）。两个 PATH 时不能再带 `--baseline`/`--compare`/`--explain`。

## 常见报错速查

| 报错 | 触发场景 | 解法 |
|---|---|---|
| `Got unexpected extra argument` | 老版本对 `diff old.conf new.conf` 的报错 | 升级到 v0.20.0，直接 `cfgdrift diff OLD NEW` |
| `parse error at line ...` + `.conf` 文件 | HOCON/nginx 内容按 ini 解析 | 报错下方有 `hint:`，按提示 `--format nginx` 或 `--format toml` |
| `--baseline is required (or use --compare)` | `diff PATH` 没带基线 | 先 `cfgdrift scan ./config --save-as-baseline NAME` 再 `diff PATH --baseline NAME` |
| `path is required with --source local` | `scan` 没给路径 | `cfgdrift scan ./config [--save-as-baseline NAME]` |
| `diff accepts at most two PATHs (OLD NEW)` | 给了 3 个及以上 PATH | 双文件对比只收两个 PATH |

## 云/部署态采集（v0.14.0 / v0.19.0）

`--source`（别名 `--adapter`）把采集来源从本地文件扩展为部署态真实配置。`k8s` 采集器通过只读 `kubectl` 子进程（零 SDK 依赖，只读白名单 `get`/`cluster-info`）抓取资源条目，接入现有解析 / diff / 脱敏 / 约束 / hint 全链路：

```bash
cfgdrift baseline create prod --source k8s --namespace prod        # 存 ConfigMap 部署态基线
cfgdrift baseline create prod --source k8s --kind secret           # 存 Secret 基线（只存指纹）
cfgdrift scan --source k8s --baseline prod --kind all              # ConfigMap + Secret 一并比对
cfgdrift daemon start --source k8s --baseline prod --kind secret   # 周期巡检 Secret 漂移
```

- 资源选择：`--kind configmap|secret|all`（v0.19.0，G7）——默认 `configmap`（零噪音，与 v0.18.0 逐字节一致），`secret` 只采 Secret，`all` 显式合并两者（configmap 先、secret 后）；不带 `--kind` 的既有命令行为不变
- **Secret 脱敏是结构性保证**（v0.19.0）：Secret 的 `data`（base64 解码）+ `stringData` 值在采集出口即被指纹化（`sha256:<16hex>`）——基线 / 报告 / 告警 / Web / hint 五个出口结构上只可能碰到指纹；显示层对 Secret 条目**整体强制打码**（`is_secret` 标记或 `/secret/` 路径段，无视键名），终端/JSON/CSV/HTML/告警/Web/hint 一律显示 `******`，基线文件被拖库也带不出明文
- 过滤：`--namespace a,b`（逐命名空间采集）、`--label-selector app=gateway`（透传 `kubectl -l`）
- 来源标注：非 local 漂移项在终端 / `--json` / 告警 payload 均带 `source`（`k8s/<ns>/<name>/<key>` 伪路径、Secret 为 `k8s/<ns>/secret/<name>/<key>`，`line` 恒为 null）；终端 source 行按资源区分 `ConfigMap=<name>` / `Secret=<name>`
- 降级清晰：kubectl 缺失 / 集群不可达 → 可读错误退出码 2；空结果 → 空报告退出码 0（D6 守卫防误报）
- 默认 `--source local` 行为与 v0.13.0 逐字节一致（零噪音契约）
- 扩展点：`COLLECTORS` 注册表挂一项即新增来源（AWS SSM 等后续版本）

## 原生告警通道（v0.15.0）

漂移告警从「webhook / 邮件 / 脚本」三通道升级为**六通道**——新增 **Slack**（Incoming Webhook）、**Microsoft Teams**（Workflow Webhook 卡片）与 **PagerDuty**（Events API v2）三个原生通道。每个通道一个专用渲染器 + 纯 HTTP POST 发送器（标准库 `urllib.request`，零新增依赖），**完全复用**既有 Channel 基类与调度链（规则级重试 / 10 分钟防抖 / 事件落库 / 审计全部白拿）：

```bash
cfgdrift alert add --name ops-slack --type slack --severity WARN \
    --webhook-url https://hooks.slack.com/services/T.../B.../xxx
cfgdrift alert add --name ops-teams --type teams --severity WARN \
    --webhook-url https://<tenant>.webhook.office.com/webhookb2/<token>/IncomingWebhook/<id>/<key>
cfgdrift alert add --name oncall --type pagerduty --severity CRITICAL \
    --routing-key "{env:CFGDRIFT_PD_ROUTING_KEY}"     # {env:VAR} 引用，明文不落盘
cfgdrift alert test --rule ops-slack                  # 连通性验证 exit 0/2
```

- **severity 统一映射**：CRITICAL → Slack/Teams `#e11d48`、PagerDuty `critical`；WARN → `#f59e0b` / `warning`；INFO → `#64748b` / `info`（`channels.py` 单点常量表，三渲染器共用）
- **凭据不落盘**：`webhook_url` / `routing_key` 支持 `{env:VAR}` 在发送时展开；错误消息只含 `scheme://host`，不回显 webhook token / routing key
- **零噪音**：不配置新通道时，CLI / 告警 payload / Web / daemon 行为与 v0.14.0 逐字节一致

## 列表重排检测（v0.16.0）

列表 diff 从「按索引逐位比较」升级为 **LCS 序列匹配**（标准库 `difflib`，零新增依赖）：元素按「类型 + 语义值」指纹配对，**内容不变仅位置变**的元素报 INFO 级 `reordered`，不再误报为多处 `modified`；真新增 / 真删除 / 真修改照常判定。

```bash
cfgdrift diff ./app.yaml --baseline prod          # 默认开启重排检测
cfgdrift diff ./app.yaml --baseline prod --no-reorder   # 恢复 v0.15.0 逐位行为
```

- **REORDERED 语义**：`[alice,bob,carol]` → `[carol,alice,bob]` = 3 条 `reordered`（INFO），**不计入退出码**（纯重排 exit 0，CI 友好）；混合场景 `[a,b,c,d]`→`[a,c,b,e]` = b/c 重排 + d 删除 + e 新增
- **可精确控制**：`change_type=reordered` 可用于 ignore 规则（静默）与 severity 规则（如把 ingress host 顺序提升为 WARN）
- **零噪音契约**：不含重排的 diff 与 v0.15.0 逐字节一致（`reordered` 计数 / `reorder` 详情仅非零时输出）；`--no-reorder` 下完全恢复 v0.15.0 行为
- **约束语义**：索引引用 = 位置引用，随重排移动（`hosts[2].port` 按新列表 index 2 评估）；内置 20 条约束均为 dict 路径，零改动
- corpus 迁移评估：`benchmark/corpus-v1/results/reorder_migration.md`（232 实例中 9 个重排实例、223 个非重排实例逐字节一致）

## 📚 文档站

| 主题 | 链接 |
|------|------|
| 首页 / 总览 | [docs-site/index.html](./docs-site/index.html) |
| 安装（双 wheel / 构建 / 环境变量 / 双模式差异） | [installation.html](./docs-site/installation.html) |
| 快速上手 | [quickstart.html](./docs-site/quickstart.html) |
| CLI 参考（完整命令清单） | [cli.html](./docs-site/cli.html) |
| Web 仪表盘 | [web-dashboard.html](./docs-site/web-dashboard.html) |
| 告警（三通道 / 重试 / 静默 / 趋势） | [alerting.html](./docs-site/alerting.html) |
| 一致性约束 | [constraints.html](./docs-site/constraints.html) |
| corpus 基准语料 | [corpus.html](./docs-site/corpus.html) |
| 自愈（remediate + hint） | [self-healing.html](./docs-site/self-healing.html) |
| 示例 gallery | [gallery.html](./docs-site/gallery.html) |
| 贡献指南 | [contributing.html](./docs-site/contributing.html) |
| FAQ | [faq.html](./docs-site/faq.html) |

## 核心特性（按用户价值，不按版本）

### 语义 diff 与严重度

- 检出新增 / 删除 / 修改 / 类型变化四类漂移，忽略注释 / 缩进 / 键序等格式噪音；快照结构 `{relpath: tree}`，文件级新增 = INFO、删除 = CRITICAL。
- 目录扫描约定：扩展名识别 `.json` / `.yaml|.yml` / `.toml` / `.ini|.cfg|.conf` / `.xml` / `.properties` / `.env` / `.nginx`（v0.18.0 新增四格式；`.conf` 仍映射 ini），未知扩展名跳过并告警；单文件未知扩展名需显式 `--format`；列表 diff 按索引比较（v0.16.0 起支持重排检测）。
- 数据目录默认 `~/.cfgdrift/`，可用 `CFGDRIFT_HOME` 或 `--store PATH` 覆盖。
- **退出码契约**：`0`=无漂移，`1`=检出漂移，`2`=错误——可直接接入 CI 门禁。
- 多环境基线对比：`compare ENV1 ENV2...`（头部展示 `compare A -> B (vX vs vY)`，支持 `environments.yaml` 映射）；`report --html` / `--json` / `--csv` 导出报告。

### 一致性约束与约束挖掘

- 在语义 diff 之上叠加约束检查层，**仅报告与本次漂移关联**的约束破坏（零噪音契约）。
- 五类约束：`range` / `enum` / `conditional_required` / `correlation` / `mutual_exclusion`；diff / scan / daemon 默认启用内置库（20 条，`--no-builtin` 关闭，`--constraints` 追加），`constraint add|list|remove|disable|enable` 管理用户规则。
- `constraint mine` 从历史扫描 / 语料挖掘候选（`mined_candidates.yaml`，`enabled: false` 不自动生效），人工确认后一键转正。

```bash
cfgdrift diff ./config --baseline prod --constraints extra.yaml
cfgdrift constraint mine --min-support 5 --source scans
```

### daemon 与告警

- 后台常驻周期扫描，检出漂移触发 webhook / 邮件 / 脚本 / Slack / Teams / PagerDuty 六通道告警；防抖去重 + 失败重试（规则级 `--retry-count` / `--retry-delay` 可配）。
- 开机自启：`daemon enable-autostart|disable-autostart|autostart-status`（systemd / launchd / schtasks，幂等语义，`--dry-run` 预览）。
- 规则级静默 `alert mute NAME --until <ISO>` + 事件 ack；daemon 健康可观测（`error_rate` 聚合）。

```bash
cfgdrift daemon enable-autostart --target /etc/nginx --baseline prod --interval 300
cfgdrift alert add --name nginx-webhook --type webhook --url http://x --retry-count 5
```

### 自愈闭环（remediate + hint）

- `cfgdrift remediate --baseline NAME [--apply]` 按 `<home>/remediate.yaml` 策略把漂移键**精确回滚**回基线值：只改写目标键文本区间，注释 / 缩进 / 键序 / 其它键逐字节保留；写前备份 + 原子写 + 写后校验；默认 dry-run 预览。
- `daemon start --remediate` 自动修复闭环；每次动作落 `remediation_log` 审计表（Web「自愈审计」视图）。
- G4 修复建议：每条 CRITICAL/WARN 漂移自动生成 `[hint]`（期望值 + 回滚命令 + 溯源）；`--no-hint` 一键恢复旧输出（零噪音）。

```bash
cfgdrift remediate --baseline prod            # dry-run 预览
cfgdrift remediate --baseline prod --apply    # 执行回滚
cfgdrift diff ./config --baseline prod --no-hint   # 关闭 hint
```

### Web 仪表盘（10 视图）

- `cfgdrift serve` 启动本地 Web 仪表盘（`127.0.0.1:8080`，需 `[web]` extra）。
- 10 视图：时间线（搜索 / 筛选 / 分页）、严重度分布（饼图点击联动）、环境对比、报告（含「导出 HTML」）、告警（规则管理 / 趋势图 / 事件 ack）、约束（生效列表 / 挖掘候选 / 违反）、corpus 统计、自愈审计等。

```bash
pip install "cfgdrift[web]"
cfgdrift serve
```

### Web 认证（v0.17.0）

把 dashboard 共享给团队时，用 **Bearer token** 给全部 `/api/*` 路由（含回滚 / 改规则 / 发告警等写操作）上锁——**不配置 token 时服务与 v0.16.0 逐字节一致**（零噪音，默认匿名）。

```bash
cfgdrift web token                 # 生成一个 URL-safe token（43 字符，一次一行）
cfgdrift serve --token <TOKEN>     # 启动时开启认证
CFGDRIFT_WEB_TOKEN=<TOKEN> cfgdrift serve   # 或走环境变量（CLI --token 优先）
```

- 认证开启后：`Authorization: Bearer <token>` 缺失 / 错误 / 缺 `Bearer ` 前缀 → `401` + `WWW-Authenticate: Bearer`（写操作零副作用）；对 token 的响应与 v0.16.0 逐字节一致。
- 前端行为：任一 `/api/*` 请求返回 401 → 弹登录层；登录成功写入 `localStorage`（key `cfgdrift_web_token`）并自动重渲染，刷新页面保持登录态。**未启用认证时前端不弹层、无探针请求**。
- 安全提示：绑到非回环地址（如 `0.0.0.0`）且未配置 token 时，启动会向 stderr 打印警告（仅提示，不拦截启动）。token 只回显「已启用/未启用」状态，从不打印 token 值。

### corpus 基准语料 + kappa

- `corpus init|fetch|export|validate` 从真实项目 git 历史挖掘配置变更对，标准化为 `instances.jsonl`（`local_path` 离线采集 / CI 安全）。
- `corpus annotate` 双人标注 + `corpus kappa`（Cohen's kappa + 混淆矩阵）+ `corpus stats`；`kappa --export` 导出论文附录。

### 敏感值脱敏

- 终端 / JSON 报告 / HTML 报告 / Web API / 告警 payload 五出口对 `password` / `token` / `secret` 等 13 类敏感键自动打码（`masking.yaml` 可定制；数据库始终保存原始值）。

### 内置解析器与自定义插件

- **内置 8 格式（v0.18.0）**：在 JSON / YAML / TOML / INI 之上新增 **XML**（`xml.etree` 标准库，属性→`@attr`、混合文本→`$text`、重复标签→list、值恒为字符串）、**Java properties**（`=`/`:`/首空白分隔、`#`/`!` 注释、`\` 续行、`\uXXXX` 转义、键保持扁平）、**.env**（`KEY=VALUE`、`export` 前缀剥除、引号剥除、行尾注释剥离）、**nginx**（手写块状态机，`server` 无参块→恒定 `list[dict]`、命名块→组 dict、数字标量化、强制分号校验）——全部纯 Python 零新增依赖；`--format xml|properties|env|nginx` 显式可用，auto 识别 `.xml` / `.properties` / `.env` / `.nginx` 扩展名；`.conf`/`.cfg` 保持 ini（零噪音）。
- `--format <plugin>` 支持自定义解析格式：插件返回原始树，由引擎统一归一化为语义树；可选 `build_line_map` 提供 `file:line` 行号。
- 方式 A 装饰器 `@register_plugin(...)` 进程内注册（import 即生效）；方式 B entry point `[project.entry-points."cfgdrift.parsers"]` pip 打包分发。
- 完整可运行示例（含 pytest 测试）：[`examples/mydsl-parser`](./examples/mydsl-parser)。

```bash
pip install -e examples/mydsl-parser
cfgdrift scan app.dsl --format mydsl        # 或按 .dsl 扩展名自动识别
```

### 双 wheel / C 加速

- 双 wheel 发布：纯 Python 通用 wheel（默认）+ CPython 3.13 C 加速平台 wheel + sdist；pip 标签优先级自动分流。
- 构建配方 / 环境变量表（`CFGDRIFT_BACKEND` / `CFGDRIFT_NO_C` 等）/ 双模式一致性差异明细：见 [installation.html](./docs-site/installation.html)。

## 示例 gallery

三段可复现 demo，一键跑通「基线 → 漂移 → 检出 → 修复建议 → 自愈」全流程：

| Demo | 场景 | 演示能力 | 一键复现 |
|------|------|----------|----------|
| nginx | 生产配置被人工改端口 | 插件解析器 + range 约束 + hint + 自愈回滚 | `bash docs/gallery/nginx/run.sh` |
| configmap | staging/prod 双 ConfigMap | 多环境对比 + correlation 约束 + 脱敏 | `bash docs/gallery/configmap/run.sh` |
| ci-gate | CI 配置漂移门禁 | 退出码契约 + JSON / HTML 报告 | `bash docs/gallery/ci-gate/run.sh` |

详见 [docs/gallery/README.md](./docs/gallery/README.md) 与 [gallery.html](./docs-site/gallery.html)。

## 贡献

欢迎提交 issue 与 PR。请先阅读 [贡献指南](./docs-site/contributing.html)。开发环境：`pip install "cfgdrift[dev]"`，测试用 `pytest`。

## FAQ

常见问题见 [文档站 FAQ](./docs-site/faq.html)。

## License

本项目采用 **MIT License**（详见根目录 [`LICENSE`](./LICENSE) 文件）。

MIT 许可允许你自由使用、修改与分发本项目（包括商业用途），只需保留原始版权声明与许可声明即可。

## 版本历史

各版本增量（v0.20.0 → v0.1.0）见 [docs/CHANGELOG.md](./docs/CHANGELOG.md)（英文版 [docs/CHANGELOG.en.md](./docs/CHANGELOG.en.md)）。
