Changelog

View Source

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

Unreleased

[0.7.0] - 2026-08-01

Added

  • ExQuality.Finding, a structured representation of a single actionable problem (file, line, column, severity, check, message, and the raw tool output it came from)

  • Stage results may carry findings; ExQuality.Stage.findings/1 reads them

  • Credo parses its issues into findings, and failure output renders them grouped by file and sorted by line

  • Stages without a parser, and output that does not parse, still print in full: findings never replace output they did not account for

  • Every stage that is considered and not run now prints a line saying so, with the reason (○ Dialyzer: skipped (--quick), ○ Doctor: skipped (:doctor not installed), ○ Credo: skipped (disabled in .quality.exs))

  • ExQuality.Config.skip_reason/2 and ExQuality.Stage.skipped/2

  • ExQuality.Umbrella, which answers which child apps exist, where they live, and what they declare

  • Findings carry the umbrella app their file belongs to, and rendered output is grouped by app

  • Test failures name the apps they came from (3 of 4,180 failed (web: 3))

  • mix quality --report PATH writes a JSON report of the run, and --format json puts it on stdout with the human output on stderr. A caller can route on which stage failed and on its findings instead of scraping the console or re-running the tool

  • ExQuality.Report, which builds that report from the same results the human output is rendered from

  • A Sobelow security stage, auto-enabled on :sobelow, with --skip-sobelow to turn it off. Findings at or above the project's exit: threshold block the run and are rendered; the rest are reported as a count (2 blocking findings (1 high, 1 medium), 3 informational not shown) and shown under --verbose or sobelow: [show_informational: true]

  • The Sobelow stage runs once per Phoenix child app in an umbrella, where mix sobelow alone finds nothing to scan, and tags each finding with its app

  • ExQuality.Umbrella.app_deps/0, the child dependencies keyed by app

  • Coverage without ExCoveralls: a project that sets test_coverage: [summary: [threshold: N]] is measured with Elixir's own mix test --cover. In an umbrella the run exports per app and aggregates with mix test.coverage, so a module exercised by another app's tests no longer reads as 0%

  • A failing coverage check reports the modules under the threshold as findings, with their source files, instead of the whole per-module table

  • test: [coverage: true | false] in .quality.exs, to measure coverage in a project that states no threshold, or to never measure it

  • :native_coverage in ExQuality.Tools.detect/0, true when :excoveralls is absent

  • mix quality.plt, which builds the Dialyzer PLT outside a run so a container image or CI job can cache it instead of paying for it inside a check

  • A run that has to build the PLT says so while it happens (⋯ Dialyzer: building PLT (this is a one-time cost)), instead of a multi-minute wait behind a stage that prints one line at the end, and reports it afterwards (No warnings (PLT built this run), stats.plt_built)

  • ExQuality.Plt, which recognises PLT work in dialyxir's output

  • ExQuality.OutputCollector.new/1 takes an :on_line handler, called with each line of a command's output as it arrives

  • ExQuality.Json, which reads a JSON document out of output a compiler or a tool also wrote to

  • Guides under docs/, shipped with the package and published with the docs: configuration, stages, reports, umbrella projects, and CI and pre-commit

  • ExQuality.Finding.relative_path/1, which normalises a path a tool reported into one relative to the run's root

Changed

  • The README leads with the output contract - a passing stage costs one line, every stage the run considered is reported with its reason, a failure renders as findings with file:line - instead of a feature list, and the reference detail it carried moves into docs/. The comparison table is gone: it made unverifiable claims about other tools that would rot
  • usage-rules.md is organised around what an agent has to decide: which command to run, what each line shape means, what to do about each stage's findings, and the fixes that are never acceptable. The anti-fixes are collected in one place rather than scattered, and cover skipping a failing test, adding a --skip-* flag and weakening a check, not just coverage and Sobelow thresholds
  • The package description names the tools, the output and the audience, so it is findable by what someone would search for on Hex
  • A passing run and mix quality.init end with rather than , matching the stage lines
  • :doctor moves to ~> 0.23, which requires decimal ~> 3.1 and clears GHSA-rhv4-8758-jx7v, an unbounded exponent in Decimal.new that enables unauthenticated denial of service. :jason moves to 1.4.5, the first release whose optional :decimal requirement admits 3.x
  • Dialyzer runs with --format short --format dialyxir. Each warning's one-line form becomes a finding naming the warning (no_return, pattern_match), and the warning count is the number of them instead of a count of lines shaped like file.ex:12:, which also counted PLT chatter and any explanation that named a second file. dialyxir's long explanation of each warning is still printed, so it stays in the stage's output and in the JSON report
  • The security audit runs mix deps.audit --format json. Each vulnerability becomes a finding against the lockfile, naming the advisory, the version in use and the version that fixes it, instead of being counted by searching the human output for Advisory: and severity: high. Unused dependencies become findings too. stats.high_severity and its siblings are replaced by stats.vulnerabilities_by_severity
  • Credo runs with --format json. Findings name the check that produced them (Credo.Check.Readability.ModuleDoc) instead of its category, the issue count is the number of issues rather than a summary-line parse, and an issue that credo reported is never dropped for having an unfamiliar line shape. A stage summary now reads 5 issues (2 readability, 3 design)

Fixed

  • Findings report a path relative to the run's root. mix_audit reports an absolute lockfile, so a vulnerability rendered as /Users/someone/code/app/mix.lock: longer to read, not what a reader would type, different between a laptop and CI for the same problem, and comparing as a different finding. It is the same base ExQuality.Umbrella.app_for_path/2 matches, so a finding's file and its app can no longer disagree about where it is. A path outside the project root stays absolute

  • A run that stops at a compile error now reports the analysis stages it never reached as skipped, instead of saying nothing about them

  • A disabled or uninstalled stage no longer vanishes from the output, where it read as a stage that passed

  • Tool auto-detection reads every umbrella child app's dependencies, not just the root's. An umbrella root usually declares no deps, so credo, dialyzer and friends were reported as not installed and a run that checked almost nothing passed

  • Test statistics sum every app's summary line instead of reporting the first app's numbers as the whole suite's

  • Coverage reads every [TOTAL] line; when apps are measured separately the lowest leads, with the per-app numbers alongside it

  • The coverage threshold is read from coveralls.json's coverage_options, where excoveralls actually writes minimum_coverage. Only the top level was looked at, so a project configuring it the standard way had no threshold enforced

  • An integer threshold (minimum_coverage: 70) no longer crashes the summary

  • The Format stage reports a failing mix format instead of discarding its exit code. A file with a syntax error names no .ex file to count, so the first line of the run was a green tick on a broken file

  • The Format stage reports a project with no .formatter.exs as skipped, rather than failing the run over a config file it never had

  • .quality.exs is read from the project root instead of the working directory, and an umbrella child with no file of its own now reads the umbrella root's

0.6.0 - 2026-04-05

Fixed

  • Output full, non-truncated content when errors occur.

0.5.0 - 2026-02-23

Changed

  • Condensed usage-rules.md by removing first-time setup, auto-detection, and detailed configuration sections that aren't needed for day-to-day LLM usage

0.4.0 - 2026-02-23

Fixed

  • Include :jason as a runtime dependency instead of dev/test only, fixing compilation warnings in host projects
  • Pass --no-compile to mix dialyzer to avoid race conditions with parallel analysis stages competing over _build/dev
  • Mark tests using File.cd! as async: false to prevent intermittent compilation failures from global working directory changes

Changed

  • Updated usage-rules.md to instruct LLMs not to truncate mix quality output

0.3.0 - 2026-02-03

Added

  • Test options pass-through: Pass extra arguments to mix test or mix coveralls:
    • Via CLI using -- separator: mix quality --quick -- --only integration
    • Via config file: test: [args: ["--only", "integration"]]
    • CLI args override config file args (no merge)
    • Supports any test flags: --only, --include, --exclude, --seed, etc.

0.2.0 - 2026-01-09

Added

  • mix quality.init now automatically configures ExCoveralls in mix.exs when coverage is selected:
    • Adds test_coverage: [tool: ExCoveralls] to project configuration
    • Adds preferred_cli_env settings for all coveralls commands
    • Smart detection prevents duplicate configuration
    • Properly indents to match existing project style

0.1.0 - 2026-01-09

Added

  • Initial release of ExQuality (formerly Quality)
  • Three-phase execution pipeline:
    • Phase 1: Auto-fix (format)
    • Phase 2: Compilation (dev + test in parallel)
    • Phase 3: Parallel analysis with streaming output
  • Quality stages:
    • Format: Auto-fixes code formatting with mix format
    • Compile: Compiles dev + test environments in parallel with warnings as errors
    • Credo: Static analysis with --strict mode (configurable)
    • Dialyzer: Type checking with graceful PLT handling
    • Doctor: Documentation coverage checking
    • Gettext: Translation completeness validation
    • Test: Test suite with optional coverage via excoveralls
  • Quick mode (--quick):
    • Skips dialyzer (slow type checking)
    • Skips coverage enforcement (tests run, % not checked)
    • Perfect for rapid iteration during development
  • Auto-detection system:
    • Automatically enables stages based on installed dependencies
    • No configuration needed for standard setups
  • Configuration system:
    • 4-tier precedence: Defaults → Auto-detection → .quality.exs → CLI flags
    • Project-level customization via .quality.exs
    • Per-stage enable/disable controls
    • CLI flags for runtime overrides
  • Streaming output:
    • Results display as each stage completes
    • No interleaving (serialized via Quality.Printer)
    • Fast stages provide immediate feedback
  • Actionable feedback:
    • Full tool output preserved in failure details
    • File:line references for easy navigation
    • Works for both humans and LLM coding assistants
  • CLI options:
    • --quick - Fast mode for iterative development
    • --skip-dialyzer - Skip Dialyzer type checking
    • --skip-credo - Skip Credo static analysis
    • --skip-doctor - Skip Doctor documentation checks
    • --skip-gettext - Skip Gettext translation checks
    • --verbose - Show full output even on success
  • Documentation:
    • Comprehensive README with workflow examples
    • usage-rules.md for LLM integration
    • Example .quality.exs configuration file
  • Coverage threshold:
    • Single source of truth (reads from coveralls config)
    • Respects coveralls.json or mix.exs settings
    • No duplicate configuration needed

Technical Details

  • Zero runtime dependencies
  • Optional dev dependencies: credo, dialyxir, doctor, excoveralls, gettext
  • Parallel execution using Elixir Tasks
  • Agent-based output serialization (ExQuality.Printer)
  • Collectable protocol for silent output capture (ExQuality.OutputCollector)
  • Tool detection via dependency scanning (ExQuality.Tools)
  • Deep-merge configuration system (ExQuality.Config)

Philosophy

ExQuality is designed for rapid, iterative development with confidence:

  1. Fast feedback loop with --quick mode
  2. Comprehensive verification with full mode
  3. Actionable output with file:line references
  4. Zero configuration required (works out of the box)
  5. Progressive enhancement (add tools as needed)