# Six

## Watch your Coverage
Zero-dependency Elixir coverage tool built for AI-assisted development. Wraps Erlang's `:cover` with smart defaults, function-level ignores, and a structured markdown report designed to be consumed directly by AI coding agents.
## Why Six
Erlang's `:cover` counts every executable line, including `defmodule`, `use`, `alias`, and other boilerplate that nobody considers "untested code." It also has no concept of ignoring specific functions or code blocks, and its output is designed for humans reading a terminal, not agents reading a file.
Six fixes all of that: smart defaults that exclude structural declarations, `@six :ignore` for function-level exclusions, comment directives for block-level control, and a structured markdown report at `.six/coverage.md` that tells an AI agent which functions have untested branches, with source snippets and context. Zero dependencies beyond OTP.
## Installation
```elixir
# mix.exs
def project do
[
app: :my_app,
test_coverage: [tool: Six],
# ...
]
end
def cli do
[preferred_envs: [six: :test, "six.detail": :test, "six.html": :test]]
end
defp deps do
[{:six, "~> 0.3", only: :test}]
end
```
## Usage
```bash
# Run tests with coverage (terminal table + agent report)
mix test --cover
# Or use the mix task directly
mix six
mix six --threshold 90
mix six --minimum-coverage 85
mix six --skip generated/ --skip _pb.ex
mix six --track-ignores
# Source-level detail view
mix six.detail
mix six.detail --filter auth
# HTML report
mix six.html
mix six.html --open
```
This produces two things:
1. A terminal summary table (sorted worst-first)
2. `.six/coverage.md` - a structured report an AI agent can read and act on
## Guides
- [Reading the Output](https://hexdocs.pm/six/reading-output.html) - understanding the terminal table, columns, and colors
- [Threshold vs Minimum Coverage](https://hexdocs.pm/six/threshold-vs-minimum-coverage.html) - reporting targets vs enforcement floors
- [AI Integration](https://hexdocs.pm/six/ai-integration.html) - the agent report, Claude Code slash command, and coverage-driven test writing
- [GitHub Actions](https://hexdocs.pm/six/github-actions.html) - CI setup, failing on low coverage, and partitioned suites
- [Custom Formatters](https://hexdocs.pm/six/custom-formatters.html) - implementing the `Six.Formatter` behaviour
## Ignoring code
Four mechanisms, from automatic to explicit:
### Default pattern filters
Lines matching these patterns are automatically excluded from coverage, with no configuration needed:
`defmodule`, `defprotocol`, `defimpl`, `defrecord`, `defdelegate`, `defstruct`, `defexception`, `@moduledoc`, `@doc`, `@impl`, `@behaviour`, `@callback`, `use`, `import`, `alias`, `require`, `plug`, `end`
### Log-level filtering
`Logger` macros check the configured level before evaluating their message and metadata. Test suites usually run with `config :logger, level: :warning`, so an `info` or `debug` call never evaluates its arguments, and `:cover` counts those lines as missed even though no test could cover them. `warning` and `error` calls still emit, so they stay covered.
List the levels you don't want counted, and Six excludes every matching `Logger` call from coverage, including multi-line calls and their metadata:
```elixir
# config/test.exs
config :six, ignore_log_levels: [:info, :debug]
```
This is off by default. It recognizes the standard `Logger` level calls (`debug`, `info`, `notice`, `warning`, `error`, and so on), the `Logger.log(level, ...)` form, and the deprecated `Logger.warn` alias.
### Function-level attribute
Add `use Six` to a module and tag functions with `@six :ignore`:
```elixir
defmodule MyApp.CoverBridge do
use Six
@six :ignore
def start_cover do
# Can't be tested during a coverage run
:cover.start()
end
def normal_function do
# This is still covered
:ok
end
end
```
`use Six` at the top of a file signals that the module has coverage exclusions, so you know to look for `@six :ignore` tags. The attribute applies to the immediately following `def`/`defp`/`defmacro`/`defmacrop`, even with `@doc` or `@impl` in between.
### Comment directives
For quick one-offs where you don't need `use Six`:
```elixir
# six:ignore:next
def admin_only, do: System.halt(1)
# six:ignore:start
def debug_dump do
# Everything in this block is excluded
end
# six:ignore:stop
```
Directive comments must be standalone comment lines. Six will not treat strings, heredocs, docs, or trailing inline comments that happen to contain `six:ignore:*` as coverage directives.
## Tracking ignores
When you're aiming for 100% coverage, you're either testing everything or explicitly ignoring it. Without a tally, ignores accumulate unnoticed. Enable `track_ignores` to write a `.sixignore` manifest at the project root that lists every explicit exclusion in your codebase:
```elixir
# config/test.exs
config :six, track_ignores: true
```
Or pass `--track-ignores` to `mix six`. Commit `.sixignore` so changes show up in PR diffs, and reviewers see new exclusions before they land.
```
# Generated by Six. Tracks explicit coverage exclusions.
# Commit this file — changes show up in PR diffs for review.
~r/lib\/my_app\/generated\//
lib/my_app/cover.ex compile_modules
lib/my_app/payments.ex process_refund a1b2c3d4
```
The format is stable across line shifts: `@six :ignore` entries are keyed by function name, and comment directive entries use a content hash of the ignored code. Adding or removing lines elsewhere in a file produces zero diff in `.sixignore`. The only changes you see are real ones: a new exclusion, a removed exclusion, or modified ignored code.
## Configuration
```elixir
# config/test.exs
config :six,
# Additional patterns to ignore (beyond defaults)
ignore_patterns: [
~r/^\s*@type\s/,
~r/^\s*defoverridable\s/
],
# Set to false to ONLY use your patterns, not the built-in defaults
default_patterns: true,
# Exclude log calls at these levels from coverage (default: [])
ignore_log_levels: [:info, :debug],
# Fail CI if coverage drops below this
minimum_coverage: 85.0,
# File patterns to skip entirely
skip_files: [
~r/lib\/my_app\/generated\//,
~r/_pb\.ex$/
],
# Output directory (default: .six)
output_dir: ".six",
# Write a .sixignore manifest at the project root tracking all exclusions
track_ignores: true,
# Formatters to run (default: terminal + agent)
formatters: [Six.Formatters.Terminal, Six.Formatters.Agent, Six.Formatters.HTML]
```
At runtime you can also override:
```bash
mix six --threshold 90
mix six --minimum-coverage 85
mix six --skip generated/ --skip _pb.ex
mix six --track-ignores
```
## Merging partitioned coverage
For CI setups that split tests across machines:
```bash
# Each partition exports its coverage data:
MIX_TEST_PARTITION=1 mix test --cover --export-coverage p1
MIX_TEST_PARTITION=2 mix test --cover --export-coverage p2
# Merge and generate report:
mix six --import-cover cover
```
## Acknowledgments
Six is built on top of Erlang's [:cover](https://www.erlang.org/doc/apps/tools/cover) and is inspired by [ExCoveralls](https://github.com/parroty/excoveralls) and [Coverex](https://github.com/alfert/coverex). Thank you!
## License
Copyright (c) 2026 Thomas Athanas
Licensed under the MIT License.