# Assay
[](https://hex.pm/packages/assay)
[](https://hexdocs.pm/assay)
[](https://github.com/Ch4s3/assay/actions)
Incremental Dialyzer for modern Elixir tooling. Assay reads Dialyzer settings
directly from your host app's `mix.exs`, runs the incremental engine, filters
warnings via `dialyzer_ignore.exs`, and emits multiple output formats suited
for humans, CI, editors, and LLM-driven tools.
## Features
* Incremental Dialyzer runs via `mix assay`
* Watch mode (`mix assay.watch`) with debounced re-analysis
* JSON/CLI formatters (`text`, `github`, `sarif`, `lsp`, `ndjson`, `llm`)
* `dialyzer_ignore.exs` filtering with per-warning decorations
* Igniter-powered installer (`mix assay.install`) that configures apps/
warning_apps in the host project
* JSON-RPC daemon (`mix assay.daemon`) plus an MCP server (`mix assay.mcp`)
for editor/LSP/agent integrations
## Installation
### Using Igniter (Recommended)
The easiest way to install Assay is using the Igniter-powered installer. First, add both Assay and Igniter to your dependencies:
```elixir
def deps do
[
{:assay, "~> 0.2", runtime: false, only: [:dev, :test]},
{:igniter, "~> 0.6", optional: false}
]
end
```
**Important**: Igniter must be in your `mix.exs` dependencies (not optional) for the installer to work.
Then run the installer:
```bash
mix deps.get
mix assay.install --yes
```
The installer will:
- Detect project and dependency apps
- Configure `apps` and `warning_apps` in your `mix.exs`
- Create a `.gitignore` entry for `_build/assay`
- Create a `dialyzer_ignore.exs` file
- Optionally generate CI workflow files (GitHub Actions or GitLab CI)
### Manual Installation
If you prefer not to use Igniter, you can configure Assay manually:
1. Add Assay to your dependencies:
```elixir
def deps do
[
{:assay, "~> 0.2", runtime: false, only: [:dev, :test]}
]
end
```
2. Add configuration to your `mix.exs`:
```elixir
def project do
[
# ... other config ...
assay: [
dialyzer: [
apps: :project_plus_deps, # or explicit list
warning_apps: :project # or explicit list
]
]
]
end
```
3. Create a `dialyzer_ignore.exs` file (optional):
```elixir
# dialyzer_ignore.exs
[]
```
4. Add `_build/assay` to your `.gitignore` (optional but recommended).
## Configuration
### Symbolic Selectors
Assay supports symbolic selectors for `apps` and `warning_apps`:
- `:project` - All project applications (umbrella: all apps from `Mix.Project.apps_paths()`, single-app: `Mix.Project.config()[:app]`)
- `:project_plus_deps` - Project apps + dependencies + base OTP libraries
- `:current` - Current Mix project app only (useful for umbrella projects)
- `:current_plus_deps` - Current app + dependencies + base OTP libraries
### Example Configuration
```elixir
# In mix.exs
def project do
[
app: :my_app,
assay: [
dialyzer: [
# Analyze project apps + dependencies
apps: :project_plus_deps,
# Only show warnings for project apps
warning_apps: :project
]
]
]
end
```
You can also mix symbolic selectors with explicit app names:
```elixir
assay: [
dialyzer: [
apps: [:project_plus_deps, :custom_app],
warning_apps: [:project, :another_app]
]
]
```
## Usage
### One-off analysis
```bash
mix assay
mix assay --print-config
mix assay --format github --format sarif
```
Exit codes: `0` (clean), `1` (warnings), `2` (error).
### Watch mode
```bash
mix assay.watch
```
Re-runs incremental Dialyzer on file changes and streams formatted output.
### JSON-RPC daemon
```bash
mix assay.daemon
```
Speaks JSON-RPC over stdio. Supported methods:
* `assay/analyze` – run incremental Dialyzer
* `assay/getStatus`, `assay/getConfig`, `assay/setConfig`
* `assay/shutdown`
### MCP server
```bash
mix assay.mcp
```
Implements the Model Context Protocol (`initialize`, `tools/list`, `tools/call`)
and exposes a single tool, `assay.analyze`, which returns the same structured
diagnostics as the daemon. Requests/responses use the standard MCP/LSP framing:
each JSON payload must be prefixed with `Content-Length: <bytes>\r\n\r\n`.
### Pretty-printing Dialyzer terms
Add [`erlex`](https://hexdocs.pm/erlex) to your host project's deps (e.g.
`{:erlex, "~> 0.2", optional: true}`) and pass `--format elixir` to `mix assay`
to render Dialyzer's Erlang detail blocks as Elixir-looking maps/structs (e.g.
`%Ecto.Changeset{}`) while keeping plain output available when the default
`text` format is used.
## Development
* `mix test` – unit tests (including daemon + MCP simulations)
* `mix credo` – linting/style checks
* `mix format` – formatter
* `mix assay.watch` – dogfooding watch mode on the library itself
## Status
Assay currently focuses on incremental Dialyzer runs, watch mode, the Igniter
installer, and daemon/MCP integrations. Future milestones include richer
pass-through Dialyzer flag support, additional output formats, and expanded
editor tooling.