Skip to content

Feature: cross-platform configuration file (thresholds, strict mode, disabled detectors/domains) #608

Description

@cryptiklemur

Summary

Right now most behavior is configured via CLI flags, env vars (DESLOPPIFY_PMD_THREADS, etc.), and per-invocation commands (desloppify exclude <path>). For anything that's reused across runs — strict-mode threshold, disabled domains/detectors, exclude lists, runner choice, custom thresholds — you have to either remember to pass the flags every time or wrap things in a Makefile/CI script.

It would be great to have a real configuration file that desloppify reads automatically, with sensible cross-platform discovery rules and a documented schema.

Motivation

Concrete things that today require flags, env vars, or repeated commands but would be one-time settings in a config file:

  • Disabling whole domains/detectors a project doesn't want (e.g. issue Can I disable a specific domain? #501 — user wants to disable the test_coverage domain entirely)
  • Tweaking thresholds for large_files_threshold, complexity, duplication, etc. without per-run flags
  • Choosing strict vs lenient as the default target, and the numeric target score
  • A persistent exclude list that doesn't depend on running desloppify exclude <path> per machine
  • Per-language overrides for monorepo-ish setups (the README explicitly says you must scan each project separately — config could capture each project's preferences)
  • CI vs local profile defaults (right now you pass --profile ci --no-badge every time)
  • PMD thread count, runner selection, review batching, etc.

Proposed Behavior

File format

TOML, to match the Python ecosystem (pyproject.toml, ruff, mypy via mypy.ini/pyproject, etc.) and so it can be parsed with stdlib tomllib on Python 3.11+ without an extra dep.

Two scopes

  1. Project config — lives in the repo, committed, shared by everyone working on it.
  2. User config — per-user defaults that apply when no project config overrides them. Useful for things like "always use this runner" or "never enable the badge locally".

Project config wins over user config; CLI flags and env vars still win over both. Precedence (highest to lowest):

  1. CLI flags
  2. Environment variables
  3. Project config
  4. User config
  5. Built-in defaults

Discovery — follow each OS's conventions

Project config (searched from --path upward to the repo root or filesystem root, first match wins):

  • .desloppify.toml
  • desloppify.toml
  • [tool.desloppify] table inside pyproject.toml

User config — respect the platform spec on each OS:

  • Linux / BSD: XDG Base Directory spec
    • $XDG_CONFIG_HOME/desloppify/config.toml, falling back to ~/.config/desloppify/config.toml
  • macOS: Apple's standard
    • ~/Library/Application Support/desloppify/config.toml
    • Also accept ~/.config/desloppify/config.toml as a secondary path, since many devs on macOS prefer XDG layout
  • Windows: Known Folders / %APPDATA%
    • %APPDATA%\desloppify\config.toml (i.e. C:\Users\<user>\AppData\Roaming\desloppify\config.toml)

DESLOPPIFY_CONFIG env var (and a --config <path> CLI flag) should override discovery entirely for power users and CI.

Implementation note: platformdirs is the standard cross-platform library that already encodes these conventions correctly — recommend using it rather than rolling per-OS path logic by hand.

Example schema

# .desloppify.toml — project config

[general]
# default target the agent works toward
target_score = 98
# which score mode to enforce/report by default
strict = true
# fail nonzero if score is below target when running `status` / CI
enforce_target = true
# global runner override (claude, codex, etc.)
runner = "claude"

[scan]
# baseline excludes — equivalent to running `desloppify exclude <path>` once per entry
exclude = [
  "vendor/",
  "node_modules/",
  "dist/",
  ".worktrees/",
  "build/",
]
# CI-equivalent flags so you don't have to repeat --profile ci --no-badge
profile = "ci"
badge = false

[thresholds]
large_files = 600
complexity_warn = 15
complexity_error = 25
duplication_min_lines = 40

[domains]
# disable whole domains (addresses #501)
disabled = ["test_coverage"]

[detectors]
# finer-grained: disable specific detectors without nuking the whole domain
disabled = ["unused::unused_import", "cycles"]

[detectors.large_files]
# per-detector overrides
threshold = 800

# per-language overrides — useful when one repo legitimately mixes languages
[languages.rust]
detectors.disabled = ["cycles"]  # known false positives on flat module layouts (#522, #545)

[languages.python]
thresholds.large_files = 500

[review]
# subjective review configuration
enabled = true
batch_size = 5
# skip specific subjective dimensions
disabled_dimensions = ["error_handling_patterns"]

[ci]
# overrides that apply when profile = "ci" or when invoked with --profile ci
badge = false
fail_below = 95

Behavior details

  • desloppify config show — print the merged, effective config (with the source of each value) so it's debuggable.
  • desloppify config path — print the file paths that were searched and which one was loaded.
  • desloppify config init — scaffold a starter .desloppify.toml (or [tool.desloppify] in pyproject.toml) with commented defaults.
  • Unknown keys should be a warning, not a hard error, so newer configs don't break older desloppify versions catastrophically.
  • A schemaVersion (or version) key at the top so the loader can detect and migrate old configs cleanly.
  • JSON Schema published alongside so editors can autocomplete and validate.

Why this matters

Open questions

  • Should [tool.desloppify] in pyproject.toml be first-class or just a convenience for Python projects? (My take: first-class, since many users of this tool are in Python projects already.)
  • How should multi-project monorepos express per-project config? Per---path .desloppify.toml is the obvious answer, but it might be worth supporting an array-of-tables for multiple project roots in a single root config too.
  • Should disabling a domain affect the score, or just hide it? (Probably affect it, otherwise it's a gaming vector — which conflicts with the anti-gaming design goal.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions