Regix searches for configuration in the following order (first match wins):
--config FILE or Regix(config=path)regix.yaml in the current directory.regix/config.yaml in the current directorypyproject.toml — [tool.regix] sectionregix.yaml in the git root (if workdir is inside a git repo)If no configuration file is found, Regix uses built-in defaults.
regix:
# ── Working directory ──────────────────────────────────────────────────────
workdir: . # root of the project to analyse
# ── Metric thresholds (absolute gate values) ───────────────────────────────
# Used by `regix gates` and the pyqual integration.
# NOT used for regression detection (which uses delta thresholds below).
metrics:
cc_max: 15 # max cyclomatic complexity per function
mi_min: 20 # min maintainability index (0–100)
coverage_min: 80 # min test coverage (%)
length_max: 100 # max function body length (lines)
docstring_min: 60 # min docstring coverage (%)
quality_min: 0.85 # min vallm quality score (0–1)
# ── Regression detection thresholds ────────────────────────────────────────
# A change must exceed these deltas to be reported.
thresholds:
delta_warn: 2 # emit WARNING when |delta| >= this
delta_error: 5 # emit ERROR when |delta| >= this
# Per-metric overrides (optional, overrides the defaults above)
per_metric:
cc:
delta_warn: 2
delta_error: 5
mi:
delta_warn: 5 # MI is noisier, require a larger shift
delta_error: 10
coverage:
delta_warn: 1 # even 1% coverage drop is a warning
delta_error: 5
length:
delta_warn: 10
delta_error: 25
# Structural metrics (built-in defaults shown — override as needed)
symbol_count:
delta_warn: 1 # losing even 1 function = warning
delta_error: 3 # losing 3+ functions = error (collapse)
fan_out:
delta_warn: 2 # losing 2 unique external calls = warning
delta_error: 5 # losing 5+ = shell/stub regression
call_count:
delta_warn: 3 # losing 3 total calls = warning
delta_error: 7 # losing 7+ = hollowed-out function
# ── File filtering ─────────────────────────────────────────────────────────
# Glob patterns relative to workdir.
# 'include' takes precedence: if set, only matching files are analysed.
# 'exclude' is applied after include.
include:
- "src/**/*.py"
- "mypackage/**/*.py"
# If empty (default), all .py files under workdir are included.
exclude:
- "tests/**" # test files skew CC metrics
- "examples/**" # examples are not production code
- "docs/**"
- "**/migrations/**"
- "**/*_pb2.py" # generated protobuf files
- "build/**"
- "dist/**"
- ".venv/**"
- "venv/**"
# ── Analysis backends ──────────────────────────────────────────────────────
backends:
cc: lizard # lizard | radon | both
mi: radon # radon only
coverage: pytest-cov # pytest-cov | none
quality: vallm # vallm | none
docstring: builtin # builtin (ast-based) | none
# structure and architecture backends are always enabled (pure AST, no deps)
# Run backends concurrently within a single snapshot capture
parallel: false
# ── Snapshot & cache ───────────────────────────────────────────────────────
cache:
enabled: true
dir: ~/.cache/regix # XDG_CACHE_HOME/regix if unset
max_entries: 200 # evict oldest entries beyond this count
max_size_mb: 500
# ── Loop / stagnation behaviour ────────────────────────────────────────────
# Used when Regix is called in a retry loop (e.g., inside pyqual).
loop:
stagnation_window: 2 # break if report is identical for N iterations
# ── Output ─────────────────────────────────────────────────────────────────
output:
format: rich # rich | json | yaml | toon
dir: .regix/ # directory for saved reports
filename: report # report.json / report.yaml / report.toon.yaml
show_improvements: true # include improvement entries in output
max_symbols: 100 # truncate symbol list beyond this (0 = unlimited)
color: auto # auto | always | never (rich renderer only)
# ── Gate behaviour ─────────────────────────────────────────────────────────
gates:
on_regression: warn # warn | error | block
# warn → exit 0, print warning
# error → exit 1 if any Regression with severity="error"
# block → exit 1 if any Regression regardless of severity
fail_exit_code: 1
pyproject.toml format[tool.regix]
workdir = "."
[tool.regix.metrics]
cc_max = 15
mi_min = 20
coverage_min = 80
length_max = 100
[tool.regix.thresholds]
delta_warn = 2
delta_error = 5
[tool.regix.exclude]
patterns = ["tests/**", "examples/**", "docs/**"]
[tool.regix.backends]
cc = "lizard"
mi = "radon"
coverage = "pytest-cov"
quality = "vallm"
[tool.regix.output]
format = "rich"
dir = ".regix/"
show_improvements = true
All configuration values can be overridden by environment variables. The naming convention is REGIX_<SECTION>_<KEY> in uppercase with dots replaced by underscores.
| Variable | Config key | Default |
|---|---|---|
REGIX_WORKDIR |
regix.workdir |
. |
REGIX_CC_MAX |
regix.metrics.cc_max |
15 |
REGIX_MI_MIN |
regix.metrics.mi_min |
20 |
REGIX_COVERAGE_MIN |
regix.metrics.coverage_min |
80 |
REGIX_DELTA_WARN |
regix.thresholds.delta_warn |
2 |
REGIX_DELTA_ERROR |
regix.thresholds.delta_error |
5 |
REGIX_FORMAT |
regix.output.format |
rich |
REGIX_OUTPUT_DIR |
regix.output.dir |
.regix/ |
REGIX_CACHE_ENABLED |
regix.cache.enabled |
true |
REGIX_CACHE_DIR |
regix.cache.dir |
~/.cache/regix |
REGIX_BACKEND_CC |
regix.backends.cc |
lizard |
REGIX_BACKEND_MI |
regix.backends.mi |
radon |
REGIX_BACKEND_COVERAGE |
regix.backends.coverage |
pytest-cov |
REGIX_BACKEND_QUALITY |
regix.backends.quality |
vallm |
REGIX_ON_REGRESSION |
regix.gates.on_regression |
warn |
REGIX_FAIL_EXIT_CODE |
regix.gates.fail_exit_code |
1 |
regix:
metrics:
cc_max: 15
backends:
cc: lizard
mi: none
coverage: none
quality: none
docstring: none
exclude:
- "tests/**"
- "examples/**"
regix:
metrics:
cc_max: 10
mi_min: 40
coverage_min: 90
length_max: 50
docstring_min: 80
thresholds:
delta_warn: 1
delta_error: 3
gates:
on_regression: error
regix:
output:
format: json
dir: .regix/
gates:
on_regression: error
cache:
enabled: true
dir: .regix/cache
CI workflow:
- uses: actions/cache@v4
with:
path: .regix/cache
key: regix-$-$
- run: regix compare $ HEAD --format json
- run: regix gates --fail-on error
myrepo/
├── packages/
│ ├── api/
│ │ └── regix.yaml ← api-specific thresholds
│ └── core/
│ └── regix.yaml ← core-specific thresholds
└── regix.yaml ← defaults for the root
Run per-package:
regix compare HEAD~1 HEAD --config packages/api/regix.yaml
regix compare HEAD~1 HEAD --config packages/core/regix.yaml
Or from root (each package overrides via its local regix.yaml):
for pkg in packages/*/; do
regix compare HEAD~1 HEAD --workdir "$pkg" --config "${pkg}regix.yaml"
done
pipeline: metrics: cc_max: 15 regression_errors_max: 0 # from Regix gate collector regression_warnings_max: 5
stages: - name: analyze tool: code2llm when: first_iteration
- name: validate
run: vallm batch pyqual tests --recursive --format toon --output ./project
when: always
- name: regression-check
tool: regix # runs: regix compare HEAD~1 HEAD --format toon --output .regix/
when: always
- name: test
tool: pytest
optional: true
- name: fix
run: llx fix . --apply --errors .pyqual/errors.json --verbose
when: metrics_fail
timeout: 1800 ```
If you are adding Regix to an existing project for the first time, run:
regix snapshot HEAD --format json > .regix/baseline.json
regix history --depth 20 --format json > .regix/history.json
Inspect the output to understand your current metric distribution before setting thresholds. Setting cc_max: 15 on a codebase where the median function CC is 12 will generate noise; setting it at 25 will miss real regressions.
| Metric | Conservative | Moderate | Strict |
|---|---|---|---|
cc_max |
25 | 15 | 10 |
mi_min |
10 | 20 | 40 |
coverage_min |
60 | 80 | 90 |
length_max |
150 | 100 | 50 |
delta_warn |
5 | 2 | 1 |
delta_error |
10 | 5 | 3 |
Test code legitimately has different metric characteristics than production code:
Always exclude tests/ and test_*.py from regression tracking unless you specifically want to track test code quality.
Generated files (migrations, protobuf, OpenAPI clients) should always be excluded. They may have extremely high CC and near-zero MI, which would make every comparison noisy and meaningless.
exclude:
- "**/migrations/**"
- "**/*_pb2.py"
- "**/*_pb2_grpc.py"
- "**/generated/**"
- "**/*.generated.py"
cc)Counts the number of linearly independent paths through a function. Each if, elif, for, while, except, with, assert, and, or adds 1.
mi)A composite metric combining Halstead volume, cyclomatic complexity, and lines of code. Scores above 20 are considered maintainable.
Note: MI is computed at file level (not function level). A low MI for an entire module usually indicates the module is doing too much and should be split.
coverage)Percentage of source lines executed during the test suite.
.coverage file)Regix reports coverage at file level. For function-level coverage, enable branch coverage in pytest-cov (--cov-report=json --cov-branch).
length)Number of lines in the function body (excluding blank lines and comments).
quality_score)A 0–1 score from vallm representing the LLM’s assessment of code quality for the file (documentation, naming, structure).
docstring_coverage)Percentage of public functions and classes that have a docstring.
ast)fan_out)Unique external functions or methods called from within a function body, excluding Python builtins (len, print, range, etc.). Near-zero fan-out in a function that previously had significant fan-out is a stub/shell regression signal.
structure_backend, AST)| Default warn delta: −2 | Default error delta: −5 |
call_count)Total ast.Call nodes in a function body. A simultaneous drop in both call_count and fan_out is a strong indicator that real logic was replaced by a stub.
structure_backend, AST)| Default warn delta: −3 | Default error delta: −7 |
symbol_count)Number of function and method definitions per file (file-level, symbol=None). A drop while file length stays the same or grows indicates monolith collapse.
structure_backend, AST)| Default warn delta: −1 | Default error delta: −3 |
param_count)Number of parameters excluding self/cls. Growth signals interface bloat.
structure_backend, AST)node_type_diversity)Count of unique AST statement node types in the function body. A drop suggests the body was replaced with a simpler or stub implementation.
structure_backend, AST)logic_density)Ratio of non-trivial AST statement nodes to total lines. Near zero means a sparse/empty body.
structure_backend, AST)Regix automatically detects cross-symbol patterns that cannot be expressed as single-metric thresholds. These are reported as ArchSmell objects in RegressionReport.smells, separate from Regression objects. A report fails (passed=False) if either errors > 0 or smell_errors > 0.
| Smell | Detection logic | Default severity |
|---|---|---|
god_function |
symbol_count drops ≥ 2 AND one remaining function’s cc grew proportionally |
error |
stub_regression |
fan_out drops to ≤ 1 AND call_count drops to ≤ 2 after previously being higher |
error |
monolith_collapse |
symbol_count drops while total file length stays or grows |
error |
shell_cluster |
≥ 3 functions in the same file simultaneously lose fan_out |
warning |
logic_drain |
logic_density drops across ≥ 3 functions in a file |
warning |
Running vallm batch . --recursive (or any analysis tool) on the entire project root will include:
docs/, *.md)examples/)*.yaml, *.toml)These files are typically unsupported by static analysis tools and end up counted as “failures” in pass-rate calculations, producing a misleadingly low pass rate (e.g., 56/96 = 58.3% when scanning everything vs 56/57 = 98.2% when scanning only Python source).
Always configure include or restrict the analysis to your source directories.
A function with CC=18 that had CC=18 last month is a technical debt item — it needs refactoring, but it is not a regression introduced by recent changes. Reporting it as a regression on every PR creates alert fatigue and causes developers to ignore the tool.
Regix separates these two concerns:
regix compare reports regressions (delta-based, relative to a baseline)regix gates reports absolute violations (threshold-based, point-in-time)Use both: regix gates in CI to prevent new absolute violations, and regix compare to catch regressions introduced by specific PRs.
regix compare HEAD –local ```
This compares the committed HEAD against the current working tree. It catches regressions before they enter git history, which is much cheaper to fix.