Your .nvmrc says Node 20. Your Dockerfile pulls node:18-alpine. Your CI matrix tests against Node 22. Which one is right?
conflic finds these contradictions for you. It scans your project, extracts version pins, port declarations, and other configuration values from across file formats, then tells you where they disagree.
Features
- 35 built-in extractors covering Node.js, Python, Go, Java, Ruby, .NET versions, application ports, and TypeScript strict mode
- IaC drift detection for Terraform, Kubernetes, and Helm files
- Custom extractors defined in
.conflic.tomlfor any concept you need to track - Policy rules that enforce organizational constraints (e.g., "all services must use Node >= 20")
- Cross-concept rules that detect dependency violations (e.g., "Python 3.12 requires pip >= 22.3")
- Diff-scoped scans that focus on what changed since a git ref
- Scan history and trends to track configuration integrity over time
- Multi-repo federation to detect cross-repository drift across a fleet
- Auto-fix for supported file types, with previews and backups
- LSP server with live diagnostics, hover info, go-to-peer references, and quick-fix code actions
- Multiple output formats: terminal, JSON, and SARIF
Installation
Requires Rust 1.94+.
From source:
Without the LSP server:
Quick start
What conflic knows about
Built-in concepts
| Concept | Sources |
|---|---|
| Node.js Version | .nvmrc, .node-version, package.json engines, Dockerfiles, CI workflows, .tool-versions, Kubernetes manifests, Helm values, Terraform |
| Python Version | .python-version, pyproject.toml, Dockerfiles, CI workflows, Kubernetes manifests, Helm values, Terraform |
| Go Version | go.mod, Dockerfiles, Kubernetes manifests, Helm values, Terraform |
| Java Version | pom.xml, Dockerfiles (OpenJDK, Temurin, Corretto, Semeru), .sdkmanrc, .tool-versions, CI workflows, Kubernetes manifests, Helm values, Terraform |
| Ruby Version | .ruby-version, Gemfile, Dockerfiles, .tool-versions, CI workflows, Kubernetes manifests, Helm values, Terraform |
| .NET Version | *.csproj, global.json, Dockerfiles, Kubernetes manifests, Helm values, Terraform |
| Application Port | .env / .env.*, docker-compose*.yml, Dockerfile EXPOSE, Kubernetes manifests, Helm values, Terraform |
| TypeScript Strict Mode | tsconfig*.json, ESLint configs (legacy and flat) |
CI workflows are recognized from .github/workflows/*.yml, .circleci/config.yml, .gitlab-ci.yml, and .gitlab-ci/*.yml.
Dockerfiles include variants like Dockerfile.dev. ESLint configs include .eslintrc, .eslintrc.json, .eslintrc.yml, and eslint.config.* files.
Infrastructure-as-Code (IaC) sources
conflic extracts versions and ports from IaC files, enabling drift detection between application configs and infrastructure definitions:
- Terraform (
*.tf): Lambda/Cloud Functionsruntimevalues (nodejs20.x,python3.12,java21, etc.), containerimagetags,container_portandhost_portassignments - Kubernetes (
deployment.yaml,service.yaml,statefulset.yaml,pod.yaml,job.yaml,cronjob.yaml): containerimagetags,containerPort, ServicetargetPort - Helm (
values.yaml,values.yml):image.repository+image.tagpatterns (including nested multi-service charts),port,containerPort,targetPort,servicePortkeys
IaC assertions use appropriate authority levels: Terraform resource attributes and Kubernetes container images are enforced, Helm values are declared.
How values are compared
conflic doesn't just do string comparison. It understands the semantics of each value type:
- Versions: exact values (
20.0.0), partials (20), ranges (^20,>=18 <20), and Docker tags (22-alpine) are compared using semver-aware logic - Ports: single ports, ranges (
3000-3005), and Docker mappings (3000:8080) are compared by their container port - Booleans: literal
true/false - Strings: exact equality
Authority levels
Each assertion carries an authority level that determines the severity of contradictions:
| Level | Meaning | Examples |
|---|---|---|
| enforced | Hard constraint; build breaks if wrong | Final Dockerfile FROM, CI runtime versions, docker-compose ports |
| declared | Should match, but not mechanically enforced | package.json engines, pyproject.toml, .env, pom.xml |
| advisory | Informational; nice to keep in sync | .nvmrc, .python-version, .tool-versions, non-final Docker stages |
When two assertions conflict, severity depends on the authority pair:
| Pair | Severity |
|---|---|
| enforced + enforced | error |
| enforced + declared | error |
| enforced + advisory | warning |
| declared + declared | warning |
| declared + advisory | info |
| advisory + advisory | info |
Configuration
conflic looks for .conflic.toml in the scan root. Run conflic --init to generate a starter config.
[]
= "warning" # minimum severity: "error", "warning", or "info"
= "terminal" # output: "terminal", "json", or "sarif"
= [] # extra directories or glob patterns to skip
= [] # concepts to ignore entirely
# Suppress a specific contradiction
[[]]
= "VER001"
= ["Dockerfile", ".nvmrc"]
= "Multi-stage build; final stage matches"
# Monorepo support
[]
= true
= ["packages/*", "apps/*"]
= ["node-version", "ts-strict-mode"]
# Organizational policies
[[]]
= "POL001"
= "node-version"
= ">= 20"
= "error"
= "Node 18 is EOL. All services must use Node 20+."
You can use short aliases like node, python, port in --check, skip_concepts, and --concept flags.
Custom extractors
Track any configuration value by defining custom extractors:
[[]]
= "redis-version"
= "Redis Version"
= "runtime-version"
= "version"
= "semver" # optional: "semver", "port", "boolean", "exact-string"
[[]]
= "docker-compose.yml"
= "yaml"
= "services.redis.image"
= "redis:(.*)"
= "enforced"
[[]]
= ".env"
= "env"
= "REDIS_VERSION"
= "declared"
Supported source formats: json, yaml, toml, env, plain, dockerfile.
Policy rules
Policies enforce organizational constraints independent of inter-file contradictions:
[[]]
= "POL002"
= "app-port"
= "!= 80, != 443"
= "warning"
= "Privileged ports require root."
[[]]
= "POL003"
= "python-version"
= "!= 3.8, != 3.9"
= "error"
= "Python 3.8 and 3.9 are EOL."
Version policies use semver ranges (>= 20). Port policies use port specs (!= 80). String policies use comma-separated blacklists (!= value1, != value2).
Cross-concept rules
Define dependency relationships between concepts. When one concept matches a condition, another concept must satisfy a constraint:
[[]]
= "RULE001"
= "warning"
= "Python 3.12+ requires pip >= 22.3"
[]
= "python-version"
= ">= 3.12"
[]
= "pip-version"
= ">= 22.3"
Cross-concept rules are evaluated after per-concept contradiction detection and policy evaluation. The when.matches field supports semver ranges and exact values. The then.requires field uses the same format. Findings from concept rules appear as additional entries in the scan results.
Scan history and trends
Track configuration integrity over time with scan history:
--record appends a snapshot (commit SHA, author, timestamp, finding counts) to .conflic-history.json in the scan root. This file should typically be gitignored.
--trend shows a table of historical snapshots with error/warning/info counts, plus lists of new and resolved findings between the last two scans.
--since <REF> uses git blame to determine when each finding was introduced and filters out findings that predate the given ref. This is useful in CI to answer "did this PR make things worse?"
Multi-repository federation
Scan multiple repositories and detect cross-repo drift:
Federation config (conflic-federation.toml):
[[]]
= "api-gateway"
= "../api-gateway"
= "backend"
[[]]
= "user-service"
= "../user-service"
= "backend"
[[]]
= "web-app"
= "../web-app"
= "frontend"
Each repository is scanned independently using its own .conflic.toml (if present). Repositories in the same group are compared for cross-repo drift: if the same concept has different values across repos in a group, it's reported as drift. The federation report shows per-repo finding counts and cross-repo drift entries.
The exit code is 1 if any repository has errors or if cross-repo drift is detected.
Diff scans and baselines
Diff scans
--diff <REF> scans files changed since a git ref, plus any peer files needed to evaluate impacted concepts. This keeps CI fast while still catching cross-file contradictions.
|
Baselines
Suppress known findings so you can adopt conflic incrementally:
Baselines track findings by rule ID, concept, severity, file path, and value, so new contradictions are still caught even if old ones are suppressed.
Auto-fix
The highest-authority assertion wins. Lower-authority files are updated to match. If the top-authority values disagree with each other, the concept is marked unfixable.
Supported fix targets include version files (.nvmrc, .python-version, .ruby-version), package manifests (package.json, go.mod, Gemfile, pom.xml, *.csproj, global.json), Dockerfiles (FROM tags and EXPOSE), .tool-versions, and .env port values.
Backups are written as *.conflic.bak unless --no-backup is passed. All writes are atomic.
LSP server
The LSP server provides:
- Diagnostics for contradictions and parse errors on both sides of each finding
- Hover showing the concept name, authority, all peer declarations, and contradiction status
- Go-to-peer references to jump between all files asserting the same concept
- Document symbols listing all extracted assertions in the outline view
- Quick-fix code actions using the same fix planner as
--fix - Incremental rescans with debouncing and peer-file invalidation
- Live config reload when
.conflic.tomlchanges
Set CONFLIC_LSP_SCAN_STATS=1 to log scan statistics for debugging.
Library usage
use ConflicConfig;
let root = new;
let config = load?;
let result = scan?;
Key exports: scan, scan_with_overrides, scan_diff, scan_doctor, git_changed_files, IncrementalWorkspace.
Discovery and parsing
- Respects
.gitignoreand Git exclude files - Always skips
node_modules,.git,vendor,target,dist,build,__pycache__,.tox,.venv,venv - JSON files fall back to JSON5 parsing (comments, trailing commas, single-quoted strings)
- YAML supports anchors and merge keys
tsconfigand ESLintextendschains are resolved with cycle detectioneslint.config.*files are statically parsed (not executed)- Extends targets outside the scan root are blocked and reported as
PARSE002
Rule IDs
| ID | Meaning |
|---|---|
VER001 |
Version contradiction |
PORT001 |
Port contradiction |
BOOL001 |
Boolean contradiction |
STR001 |
String contradiction |
<custom> |
Cross-concept rule violation (uses the id from [[concept_rule]]) |
POL* |
Policy violation |
PARSE001 |
File read or parse failure |
PARSE002 |
Blocked or failed extends resolution |
CONFIG001 |
Invalid custom extractor configuration |
CLI reference
| Flag | Description |
|---|---|
[PATH] |
Directory to scan (default: .) |
-f, --format |
Output format: terminal, json, sarif |
-s, --severity |
Severity threshold: error, warning, info |
--check <A,B,...> |
Only report selected concepts |
--init |
Create a template .conflic.toml |
-c, --config <PATH> |
Explicit config file path |
-q, --quiet |
Suppress output when clean |
-v, --verbose |
Also show consistent concepts |
--no-color |
Disable colors |
--list-concepts |
Print built-in extractors and exit |
--doctor |
Run diagnostic mode |
--diff <REF> |
Diff-scoped scan since a git ref |
--diff-stdin |
Diff-scoped scan from stdin paths |
--fix |
Auto-fix contradictions |
--dry-run |
Preview fixes without applying |
-y, --yes |
Skip confirmation prompt |
--no-backup |
Don't create .conflic.bak files |
--concept <ID> |
Limit fix to one concept |
--baseline <PATH> |
Suppress known findings |
--update-baseline <PATH> |
Save current findings as baseline |
--record |
Record scan in .conflic-history.json |
--trend |
Show trend report from scan history |
--since <REF> |
Only show findings introduced since a git ref |
--federate <PATH> |
Run federated scan across multiple repos |
--init-federation |
Create a template conflic-federation.toml |
--lsp |
Start the LSP server |
GitHub Action
Use conflic as a CI gate to block PRs that introduce configuration contradictions.
Quick start
- uses: onplt/conflic@v1
with:
severity: warning
fail-on: error
Inputs
| Input | Default | Description |
|---|---|---|
version |
latest |
Conflic version to install (e.g., 1.0.1) |
path |
. |
Directory to scan |
severity |
error |
Minimum severity to report: error, warning, info |
fail-on |
error |
Severity threshold that causes failure: error, warning, info, none |
diff |
"" |
Git ref for diff-scoped scan. auto = PR base SHA |
sarif-upload |
true |
Upload SARIF to GitHub Code Scanning |
baseline |
"" |
Path to baseline file |
config |
"" |
Path to .conflic.toml |
args |
"" |
Additional CLI arguments |
Outputs
| Output | Description |
|---|---|
exit-code |
Raw conflic exit code (0/1/2) |
error-count |
Number of error-level findings |
warning-count |
Number of warning-level findings |
sarif-file |
Path to generated SARIF file |
Scenarios
PR diff scan — only check changed files:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: onplt/conflic@v1
with:
diff: auto
fail-on: error
SARIF annotations — inline PR comments via Code Scanning:
permissions:
security-events: write
steps:
- uses: actions/checkout@v4
- uses: onplt/conflic@v1
with:
sarif-upload: true
fail-on: none
Baseline workflow — suppress known issues:
- uses: onplt/conflic@v1
with:
baseline: .conflic-baseline.json
diff: auto
fail-on: error
severity controls what conflic reports. fail-on controls what fails the action. This lets you annotate warnings in PRs without blocking merges.
More examples in .github/examples/.
Exit codes
| Code | Meaning |
|---|---|
0 |
Clean (no findings at or above threshold) |
1 |
Error-level finding or operational failure |
2 |
Warning-level findings present |
3 |
--init refused (config already exists) |