diffyml
A fast, structural YAML diff tool with built-in Kubernetes intelligence. One dependency, minimal attack surface, native CI annotations for GitHub, GitLab, and Gitea.
diffyml compares YAML files and shows meaningful, structured differences — not line-by-line text diffs.
📖 Full documentation: szhekpisov.github.io/diffyml
Table of Contents
- Homebrew - Scoop (Windows) - Nixpkgs - Go Install - Install script (Linux / macOS) - Linux packages (.deb / .rpm / .apk)
- Direct binary download
- Docker
- From Source
- Verifying Releases
- Output Formats
- CI Integration
- Kubernetes Support
- Directory Comparison
- Git Integration
- AI Summary
- Sensitive Value Masking
- Filtering
- Inverse Diff
- Neat Mode
- Configuration File
- Custom Colors
- All Flags
Why diffyml?
Fastest structural YAML diff tool at scale. On large (5K lines) and xlarge (50K lines) inputs diffyml is 1.5–1.9× faster than the nearest YAML-aware competitor. On small and medium files it ties within a few milliseconds — the residual overhead comes from capabilities the simpler tools lack (x509 certificate inspection, remote URL fetching, AI-powered summaries). See PERFORMANCE.md for methodology and results.
One dependency, zero surprises. A single module dependency (yaml.v3) and pure Go stdlib. Minimal attack surface, auditable in minutes.
Gets YAML right. Dotted keys, type preservation, mixed-type lists, nil values — concrete edge cases other tools get wrong. diffyml treats YAML semantics as first-class, not an afterthought.
How It Compares
| Feature | diffyml | dyff | plain diff |
|---------|---------|------|------------|
| YAML-aware (structural diff) | Yes | Yes | No (line-based) |
| Kubernetes resource matching | By apiVersion + kind + name (or generateName) | By apiVersion + kind + name | No |
| Rename detection | Yes (content similarity — handles name changes) | Yes (identifier-based — name must stay the same) | No |
| API version migration | Yes (--ignore-api-version) | No | No |
| CI annotation formats | 3 (GitHub, GitLab, Gitea) | 0 | 0 |
| Module dependencies | 1 (yaml.v3) | 23 | 0 |
| Directory comparison | Yes | No | Yes |
| Inverse diff (report unchanged values) | Yes (--unchanged) | No | No |
| Git external diff (GIT_EXTERNAL_DIFF) | Yes (auto-detect) | Manual (wrapper script) | N/A |
| Inline diff highlighting | Yes (word-level) | Yes (character-level) | No |
| Custom colors | Yes (hex, env vars) | No | No |
| Configuration file | Yes (.diffyml.yml) | No | No |
| Performance (78 KB) | 19 ms | 120 ms (6.4x) | 6 ms |
| Performance (780 KB) | 129 ms | 1,146 ms (8.9x) | 45 ms |
Comparison based on dyff v1.11.3 and diffyml v1.5.23. See PERFORMANCE.md for full benchmark methodology and results against 5 competitors. Open an issue if anything is outdated.
Installation
Homebrew
brew tap szhekpisov/diffyml
brew install diffyml
Scoop (Windows)
scoop bucket add diffyml https://github.com/szhekpisov/scoop-diffyml
scoop install diffyml
Nixpkgs
Available in nixpkgs on the unstable channel. It landed after the 26.05 branch-off, so it isn't in the current stable release yet — it will be in the next one.
# Ephemeral shell
nix shell nixpkgs#diffyml
Run without installing
nix run nixpkgs#diffyml -- old.yaml new.yaml
Install into your profile
nix profile install nixpkgs#diffyml
On NixOS, add it to environment.systemPackages:
environment.systemPackages = with pkgs; [ diffyml ];
Without flakes:
nix-env -iA nixpkgs.diffyml # or: nix-shell -p diffyml
Go Install
go install github.com/szhekpisov/diffyml@latest
Make sure $GOPATH/bin is in your PATH:
export PATH="$(go env GOPATH)/bin:$PATH"
Install script (Linux / macOS)
curl -fsSL https://szhekpisov.github.io/diffyml/install.sh | sh
Detects your OS and architecture, downloads the matching release archive, verifies its SHA256 against the signed checksums.txt, and installs to /usr/local/bin/diffyml. Customizable via env vars:
| Variable | Default | Notes |
|---|---|---|
| DIFFYML_VERSION | latest release | Pin a specific version, e.g. 1.6.1. Recommended in CI — avoids the unauthenticated GitHub API call (60 req/hr per IP) used to resolve the latest tag. |
| INSTALL_DIR | /usr/local/bin | Falls back to sudo if the directory isn't writable. |
| VERIFY | sha256 | Set cosign to verify the cosign signature on checksums.txt first (requires cosign in PATH), or none to skip verification. |
| GITHUB_TOKEN | unset | If set, used to authenticate the GitHub API call when resolving the latest version. Useful on shared CI egress IPs. |
# Pin a version, install into ~/bin, verify cosign signature too:
DIFFYML_VERSION=1.6.1 INSTALL_DIR="$HOME/bin" VERIFY=cosign \
sh -c "$(curl -fsSL https://szhekpisov.github.io/diffyml/install.sh)"
Linux packages (.deb / .rpm / .apk)
Native packages for Debian/Ubuntu, RHEL/Fedora, and Alpine (amd64 and arm64) are attached to every release. All package archives are listed in the cosign-signed checksums.txt, so you can verify before installing — see Verifying Releases. The .apk uses --allow-untrusted because nfpm-built apks aren't GPG-signed; verify the SHA256 from checksums.txt instead.
# Debian / Ubuntu
curl -fLO "https://github.com/szhekpisov/diffyml/releases/download/v1.6.1/diffyml_1.6.1_linux_amd64.deb"
sudo dpkg -i diffyml_1.6.1_linux_amd64.deb
RHEL / Fedora / openSUSE
curl -fLO "https://github.com/szhekpisov/diffyml/releases/download/v1.6.1/diffyml_1.6.1_linux_amd64.rpm"
sudo rpm -i diffyml_1.6.1_linux_amd64.rpm
Alpine
curl -fLO "https://github.com/szhekpisov/diffyml/releases/download/v1.6.1/diffyml_1.6.1_linux_amd64.apk"
sudo apk add --allow-untrusted diffyml_1.6.1_linux_amd64.apk
The binary is installed to /usr/bin/diffyml.
Direct binary download
If you'd rather not pipe a script to sh, the same archives are attached to every release for Linux, macOS, and Windows (amd64 and arm64). Linux/macOS ship as .tar.gz, Windows as .zip:
VERSION=1.6.1 # check the releases page for the latest
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')
curl -fL "https://github.com/szhekpisov/diffyml/releases/download/v${VERSION}/diffyml_${VERSION}_${OS}_${ARCH}.tar.gz" \
| tar -xz
sudo mv diffyml /usr/local/bin/
On Windows (PowerShell):
$Version = "1.6.1" # check the releases page for the latest
$Arch = if ($Env:PROCESSOR_ARCHITECTURE -eq "ARM64") { "arm64" } else { "amd64" }
curl.exe -fLO "https://github.com/szhekpisov/diffyml/releases/download/v$Version/diffyml_${Version}_windows_$Arch.zip"
Expand-Archive "diffyml_${Version}_windows_$Arch.zip" -DestinationPath .
move diffyml.exe somewhere on your PATH
See Verifying Releases below to check signatures and provenance before installing.
Docker
Multi-arch images (linux/amd64, linux/arm64) are published to GitHub Container Registry:
docker pull ghcr.io/szhekpisov/diffyml:latest
Compare two files from the current directory
docker run --rm -v "$PWD:/work" -w /work ghcr.io/szhekpisov/diffyml:latest old.yaml new.yaml
Images are built from a distroless base and run as a non-root user. Use :latest or pin to a specific version (e.g. :1.5.25).
From Source
git clone https://github.com/szhekpisov/diffyml.git
cd diffyml
go build -o diffyml
Verifying Releases
Published release artifacts are never modified or re-uploaded — each version is a one-time, append-only event. Every release includes:
- Checksums (
checksums.txt) — SHA256 hashes for all archives - Cosign signature (
checksums.txt.sigstore.json) — keyless Sigstore signature - SBOMs (
*.spdx.json) — SPDX Software Bill of Materials for each archive - SLSA provenance — Level 3 provenance attestation
Verification commands
Verify the checksums signature:
cosign verify-blob checksums.txt \
--bundle checksums.txt.sigstore.json \
--certificate-identity-regexp 'https://github.com/szhekpisov/diffyml/' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com'
Linux
sha256sum --check checksums.txt --ignore-missing
macOS
shasum -a 256 --check checksums.txt --ignore-missing
Verify SLSA provenance:
gh attestation verify diffyml_<VERSION>_linux_amd64.tar.gz \
--repo szhekpisov/diffyml
Verify the container image signature:
cosign verify \
--registry-referrers-mode=oci-1-1 \
--certificate-identity-regexp 'https://github.com/szhekpisov/diffyml/' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
ghcr.io/szhekpisov/diffyml:<VERSION>
Quick Start
# Compare two local files
diffyml old.yaml new.yaml
Compare local file against a remote URL
diffyml local.yaml https://example.com/remote.yaml
Use in CI — exit code 1 when differences found
diffyml -s deployment-old.yaml deployment-new.yaml
Use as kubectl external diff provider:
export KUBECTL_EXTERNAL_DIFF="diffyml --omit-header --set-exit-code"
Features
- 7 output formats — detailed, compact, brief, GitHub, GitLab, Gitea, JSON
- Path filtering — include/exclude paths with exact match or regex
- Remote files — compare directly from HTTP/HTTPS URLs
- Certificate inspection — inspects and compares embedded x509 certificates
- Chroot navigation — focus comparison on a specific YAML subtree
- Git integration — use as
GIT_EXTERNAL_DIFFor via.gitattributesfor YAML-only scoping - Inline diff highlighting — highlights only the changed parts within scalar values (version tags, IPs, ports) for quick scanning
- Custom colors — configurable color palette for accessibility (colorblind-friendly)
- Configuration file — project-level defaults via
.diffyml.yml(all flags supported) - Sensitive value masking — opt-in redaction of Kubernetes Secret
data/stringDataand arbitrary paths; applies to every output format - ⭐ AI-powered summaries ⭐ — natural language summaries of changes via Anthropic API
Usage
diffyml [flags] <from> <to>
Output Formats
| Format | Flag | Use case |
|--------|------|----------|
| detailed | -o detailed (default) | Human review — full context |
| compact | -o compact | Quick scan of changes |
| brief | -o brief | Summary only |
| github | -o github | GitHub Actions annotations |
| gitlab | -o gitlab | GitLab CI annotations |
| gitea | -o gitea | Gitea CI annotations |
| json | -o json | Machine-readable — piping, scripting, CI |
CI Integration
Use -s / --set-exit-code to set the exit code based on differences:
| Exit code | Meaning |
|-----------|---------|
| 0 | No differences (or success without -s) |
| 1 | Differences detected (only with -s) |
| 255 | Error occurred |
diffyml -s before.yaml after.yaml || echo "Config drift detected"
GitHub Actions — use the diffyml-action composite action (no manual binary install):
- uses: szhekpisov/diffyml-action@v1
with:
from: old.yaml
to: new.yaml
To inspect drift without failing the job, read the has-differences output:
- uses: szhekpisov/diffyml-action@v1
id: diff
with:
from: old.yaml
to: new.yaml
fail-on-diff: 'false'
output: github
- if: steps.diff.outputs.has-differences == 'true'
run: echo "Configuration drift detected"
See the action repo for the full list of inputs and outputs.
Kubernetes Support
Resources are auto-detected and matched by apiVersion + kind + metadata.name (or metadata.generateName), so diffs stay meaningful even when document order changes.
Rename detection — when resources can't be matched by identifier (e.g., kustomize configMapGenerator hash-suffix changes like app-config-abc123 → app-config-def456), diffyml pairs unmatched documents by content similarity (60% threshold) and shows field-level diffs instead of bulk add/remove. Disable with --detect-renames=false.
API migration — --ignore-api-version drops apiVersion from the matching key, so an upgrade from apps/v1beta1 to apps/v1 shows field-level diffs instead of a remove + add.
Opt out — --detect-kubernetes=false disables K8s-aware matching entirely and compares documents by position.
# Compare two Kubernetes manifests
diffyml manifests-v1.yaml manifests-v2.yaml
API migration — match by kind + name only
diffyml --ignore-api-version manifests-v1.yaml manifests-v2.yaml
Disable Kubernetes detection
diffyml --detect-kubernetes=false file1.yaml file2.yaml
Directory Comparison
diffyml accepts two directories as positional arguments. It recursively discovers all regular files in each directory (regardless of extension), matches them by relative path, and shows aggregated differences. Files that cannot be parsed as YAML are silently skipped.
This makes diffyml a drop-in KUBECTL_EXTERNAL_DIFF provider — kubectl passes two temporary directories containing extensionless temp files (e.g. apps.v1.Deployment.default.nginx), and diffyml discovers them automatically:
export KUBECTL_EXTERNAL_DIFF="diffyml --omit-header --set-exit-code"
kubectl diff -f manifests/
Git Integration
diffyml can be used as a git external diff program. Git passes 7-9 positional arguments which diffyml auto-detects — non-YAML files are skipped with a warning.
# One-off: structural diff for YAML changes in the working tree
GIT_EXTERNAL_DIFF=diffyml git diff
With flags (e.g. compact output)
GIT_EXTERNAL_DIFF='diffyml -o compact' git diff
For permanent setup, use .gitattributes — git's built-in diff handles all other file types normally:
*.yaml diff=diffyml
*.yml diff=diffyml
git config diff.diffyml.command diffyml
Color and truecolor are auto-forced (git's pager makes stdout a pipe). Use --color never to disable. --set-exit-code is silently ignored — git aborts external diff programs that exit non-zero. Parse errors are non-fatal: a warning is printed and git continues to the next file.
AI Summary
Generate a natural language summary of changes using the Anthropic API:
export ANTHROPIC_API_KEY="sk-ant-..."
Append AI summary after the diff output
diffyml --summary old.yaml new.yaml
Use with brief format — replaces brief output with AI summary
diffyml --summary -o brief old.yaml new.yaml
Use a different model
diffyml --summary --summary-model claude-sonnet-4-5-20250514 old.yaml new.yaml
The summary is appended after the standard diff output. If the API call fails, a warning is printed to stderr and the diff output is preserved. The exit code is never affected by summary success or failure.
Sensitive Value Masking
Masking is opt-in. When enabled, diffyml replaces the value of any matching diff with * before rendering — applies to every output format (including json, json-patch, and the --summary prompt). Diffs still show that a value changed, just not what the value was.
# Auto-mask data/stringData of Kubernetes Secret resources
diffyml --mask-secrets secrets-old.yaml secrets-new.yaml
Mask additional paths (e.g., a ConfigMap field that holds an API key)
diffyml --mask-path 'data.api_key' --mask-path 'data.db_url' old.yaml new.yaml
Regex variant — mask any field whose path matches
diffyml --mask-path-regexp '(?i)password|token|secret' old.yaml new.yaml
Custom placeholder
diffyml --mask-secrets --mask-placeholder '<REDACTED>' old.yaml new.yaml
Mask paths use the same dot-notation as --filter / --exclude (prefix match honored). The leading document index for multi-document files ([0], [1]) is automatically stripped before matching.
Configure defaults via .diffyml.yml so masking is always on for this repo:
mask-secrets: true
mask-path:
- "data.api_key"
mask-path-regexp:
- "(?i)password"
mask-placeholder: "*"
Filtering
# Show only changes under a specific path
diffyml --filter spec.replicas old.yaml new.yaml
Exclude noisy paths
diffyml --exclude metadata.annotations old.yaml new.yaml
Keys containing dots use bracket syntax
diffyml --exclude 'metadata.annotations[argocd.argoproj.io/tracking-id]' old.yaml new.yaml
Regex filtering
diffyml --filter-regexp 'spec\.containers\[.*\]\.image' old.yaml new.yaml
Inverse Diff
-u, --unchanged inverts the report: instead of the differences, it lists the keys/values that are equal between the two files. Equal subtrees collapse to a single entry at the highest fully-equal node, and it honors every output format, --filter/--exclude, and masking.
Filters can target descendants of a collapsed equal subtree using either numeric list indices (containers.0.image) or list identifiers (containers.app.image). The collapsed subtree remains atomic in the output: matching or excluding any descendant keeps or removes the whole entry, respectively.
# Find values that already match the chart defaults (candidates to drop)
diffyml --unchanged values.yaml chart-defaults.yaml
Comparison is at key/value granularity — map keys, list items, and whole scalars. List items are matched the same way as in a normal diff: by identifier (name/id), order-independently under --ignore-order-changes or for heterogeneous lists, and otherwise positionally. A multi-line (block) string is compared as a single scalar: if any line inside it differs, the whole value is "changed" and none of its lines are reported as unchanged. Inverse mode does not line-diff inside strings (unlike the normal diff, which shows a line-by-line diff for modified multi-line strings).
Neat Mode
--neat excludes well-known noise paths injected by the Kubernetes API server, kubectl, Helm, ArgoCD, and Flux — metadata.managedFields, metadata.resourceVersion, the entire status subtree, meta.helm.sh/release-name, helm.sh/chart, argocd.argoproj.io/tracking-id, kustomize.toolkit.fluxcd.io/*, and similar paths. The full strip list lives in doc/neat.md.
# Compare a kube-apiserver-rendered manifest against your source-of-truth
(using process substitution; diffyml reads files, not stdin)
diffyml --neat <(kubectl get deployment nginx -o yaml) source.yaml
In a kubectl/helm/argocd diff workflow
KUBECTL_EXTERNAL_DIFF="diffyml --neat" kubectl diff -f deployment.yaml
See what neat actually filtered
diffyml --neat --neat-explain old.yaml new.yaml
Per-layer opt-outs let you keep one bundle while stripping the rest:
# Show Helm chart-version diffs but still strip ArgoCD/Flux/managedFields
diffyml --neat --no-neat-helm old.yaml new.yaml
--neat-strip-path extends the bundle without rebuilding (requires --neat):
diffyml --neat --neat-strip-path '^metadata\.annotations\[my\.org/.*\]$' old.yaml new.yaml
--neat deliberately preserves spec.template.metadata.annotations[kubectl.kubernetes.io/restartedAt] (intentional rollout marker), spec.replicas, data/stringData (use --mask-secrets for those), and user-defined labels/annotations outside the canonical noise prefixes.
Configuration File
diffyml loads project-level defaults from .diffyml.yml (or .diffyml.yaml) in the current directory. CLI flags override config file values. Use --config to specify a custom path.
# .diffyml.yml
output: compact
ignore-order-changes: true
detect-kubernetes: false
filter:
- "spec.containers"
exclude:
- "status"
All CLI flags are supported as config keys (kebab-case, matching the long flag name). Unknown keys are rejected to catch typos. See .diffyml.yml.example for a complete reference with all keys and defaults.
Custom Colors
Diff colors can be customized for accessibility (e.g., colorblind-friendly palettes). Five color roles are configurable: added, removed, modified, context, and doc-name.
Colors are specified as hex (#rrggbb, #rgb).
Via config file:
# .diffyml.yml
colors:
added: "#6aa3a5"
removed: "#702d06"
Via environment variables (override config file):
export DIFFYML_COLOR_ADDED="#6aa3a5"
export DIFFYML_COLOR_REMOVED="#702d06"
diffyml old.yaml new.yaml
Available environment variables: DIFFYML_COLOR_ADDED, DIFFYML_COLOR_REMOVED, DIFFYML_COLOR_MODIFIED, DIFFYML_COLOR_CONTEXT, DIFFYML_COLOR_DOC_NAME.
All Flags
Complete flag reference
Output
| Flag | Description |
|------|-------------|
| -o, --output | Output style: detailed, compact, brief, github, gitlab, gitea, json (default detailed) |
| -c, --color | Color usage: always, never, auto (default auto) |
| -t, --truecolor | True color (24-bit): always, never, auto (default auto) |
Comparison
| Flag | Description |
|------|-------------|
| -i, --ignore-order-changes | Ignore order changes in lists |
| --ignore-whitespace-changes | Ignore leading/trailing whitespace differences |
| --format-strings | Canonicalize embedded JSON strings before comparison (suppresses formatting-only diffs) |
| -v, --ignore-value-changes | Show only structural changes, exclude value changes |
| --detect-kubernetes | Detect and match Kubernetes resources (default true) |
| --detect-renames | Detect renamed/moved Kubernetes resources by content similarity (default true) |
| --ignore-api-version | Ignore apiVersion when matching Kubernetes resources |
| -x, --no-cert-inspection | Disable x509 certificate inspection |
| --swap | Swap from/to files |
| -u, --unchanged | Inverse diff: report keys/values equal between both files instead of differences |
Filtering
| Flag | Description |
|------|-------------|
| --filter | Include only differences at specified paths (repeatable) |
| --exclude | Exclude differences at specified paths (repeatable) |
| --filter-regexp | Filter using regular expressions (repeatable) |
| --exclude-regexp | Exclude using regular expressions (repeatable) |
| --additional-identifier | Additional field for list item identification |
Sensitive Value Masking
| Flag | Description |
|------|-------------|
| --mask-secrets | Auto-mask data / stringData of Kubernetes Secret resources |
| --mask-path | Additional path to mask, dot-notation with prefix match (repeatable) |
| --mask-path-regexp | Additional path to mask, regex (repeatable) |
| --mask-placeholder | Placeholder for masked values (default *) |
Display
| Flag | Description |
|------|-------------|
| -b, --omit-header | Omit summary header |
| -g, --use-go-patch-style | Use Go-Patch style paths |
| --multi-line-context-lines | Context lines for multi-line strings (default 4) |
Chroot
| Flag | Description |
|------|-------------|
| --chroot | Change root level for both files |
| --chroot-of-from | Change root level for the from file only |
| --chroot-of-to | Change root level for the to file only |
| --chroot-list-to-documents | Treat chroot list as separate documents |
AI Summary
| Flag | Description |
|------|-------------|
| -S, --summary | Generate AI-powered natural language summary (requires ANTHROPIC_API_KEY) |
| --summary-model | Model for AI summary (default claude-haiku-4-5-20251001) |
Other
| Flag | Description |
|------|-------------|
| --config | Path to config file (default .diffyml.yml in current directory) |
| -s, --set-exit-code | Exit code 1 if differences found |
| -h, --help | Show help |
| -V, --version | Show version information |
Library Usage
diffyml can be used as a Go library for programmatic YAML comparison.
import "github.com/szhekpisov/diffyml/pkg/diffyml"
// Compare two YAML documents
from, _ := diffyml.LoadContent("old.yaml")
to, _ := diffyml.LoadContent("new.yaml")
diffs, err := diffyml.Compare(from, to, &diffyml.Options{
DetectKubernetes: true,
})
if err != nil {
log.Fatal(err)
}
// Format the differences
formatter, _ := diffyml.FormatterByName("compact")
fmt.Print(formatter.Format(diffs, diffyml.DefaultFormatOptions()))
See the package documentation for the full API reference.
Security & Code Quality
Supply chain. Releases are signed with cosign (keyless Sigstore), ship SPDX SBOMs for every artifact, and carry SLSA Level 3 build provenance. Published tags are immutable. See Verifying Releases for verification commands. The repo is tracked by OpenSSF Scorecard (badge above).
Continuous checks. Every push and PR is scanned by:
- govulncheck — known vulnerability detection (runs on
mainweekly as well) - zizmor — GitHub Actions workflow security scanning
- golangci-lint running:
Test quality. 1,500+ tests (unit, e2e, fuzz, property-based), 99%+ aggregate code coverage across the core and CLI packages, mutation testing gated per-PR (no LIVED mutant on changed lines) with an 85% post-merge efficacy floor. CI enforces a 99% aggregate coverage floor.
Reporting vulnerabilities. See SECURITY.md — preferred path is a private GitHub Security Advisory.
Contributing
Contributions welcome! Open an issue for bugs or feature requests.
Development setup
Prerequisites: Go 1.26.9+, pre-commit
git clone https://github.com/szhekpisov/diffyml.git
cd diffyml
pre-commit install
Pre-commit hooks run automatically on every commit:
| Hook | What it checks |
|------|---------------|
| gofmt | Code formatting |
| go vet | Static analysis |
| check-coverage | Aggregate core + CLI coverage threshold (99% overall) |
| govulncheck | Known vulnerabilities |
| golangci-lint | 7 linters (errcheck, gocritic, gosec, govet, ineffassign, misspell, staticcheck) |
Useful Make targets:
make test # run all tests
make ci # full CI pipeline locally (fmt + vet + test + coverage + security)
make bench # run benchmarks
make bench-compare # compare against alternative tools (see doc/PERFORMANCE.md)
make coverage # generate HTML coverage report
make mutation # run mutation testing (requires gomutants)
CI pipelines (run on every push and PR):
- Tests — unit tests + coverage thresholds
- Security & Static Analysis — govulncheck + golangci-lint (also runs weekly)
- Benchmark — performance regression tracking
- Mutation Testing — test quality validation via gomutants
Acknowledgments
This project is heavily inspired by dyff, and it wouldn't be possible without the hard work of the maintainers and contributors of that project.
License
MIT.
If you find this project useful, please consider giving it a ⭐ — it helps others discover it.