Status: Phase 2 In Progress (Published to Hex.pm)
This roadmap is managed by
rmap. The task tables below are rendered fromroadmap/tasks.toml(canonical source) — edit tasks there (or viarmapcommands), then runrmap render. Prose outside the marker pairs is preserved.
Goal: Provide AI-friendly JSON test output for ExUnit, enabling AI editors like Claude Code to efficiently parse and reason about test results.
Target Users:
- AI editors (Claude Code, Cursor, etc.)
- CI/CD systems needing structured test output
- Developers wanting machine-parseable test results
Success Metrics:
- Claude Code can parse output without grep/tail/sed
- All ExUnit test states correctly represented in JSON
- Package published to Hex.pm
- Stable JSON Schema v1 documented and enforced by tests
Tech Stack:
- Elixir 1.18+ (for built-in
:jsonmodule) - ExUnit (formatter API)
- No external dependencies for core functionality
Focus phase: 2 — Future Enhancements (9 of 19 done · 0 in progress)
Last shipped: Task 30 — Automatic retry-on-flaky (default ON) on 2026-06-01
Up next: Task 20 — Consistent failure_message field (Schema v2) [D:2/B:7/U:7 → Eff:3.5] 🎯
Goal: Working JSON formatter with basic filtering options via mix test.json.
Success Criteria:
-
mix test.jsonoutputs valid JSON with all test results -
--summary-onlyflag works -
--failures-onlyflag works - All edge cases handled (Unicode, long values, setup failures)
- Tests pass with good coverage
- Published to Hex.pm (v0.1.3)
- Output ordering is deterministic (file, line, name)
The ai_output bundle folds in the former "Phase 1.5: AI-Friendly Enhancements"
(relative paths, tighter truncation, --compact), all shipped in the v0.1.x line.
11 tasks. See CHANGELOG.md.
Note: Prioritized by ROI — rmap computes Eff = (B+U)/(2·D). Done features
shipped across the v0.3.x–v0.4.x line; pending features sorted by efficiency.
| Task | Status | Notes |
|---|---|---|
| Task 13 | ✅ | 🎁 output_fields · Default to failures-only output [D:3/B:9/U:9 → Eff:3.0] 🎯 |
| Task 17 | ✅ | 🎁 output_fields · --first-failure quick-iteration mode [D:2/B:5/U:5 → Eff:2.5] 🎯 |
| Task 18 | ✅ | 🎁 output_fields · --filter-out 'pattern' [D:4/B:8/U:8 → Eff:2.0] 🎯 |
| Task 20 | ⬜ | 🎁 output_fields · Consistent failure_message field (Schema v2) [D:2/B:7/U:7 → Eff:3.5] 🎯 |
| Task 21 | ⬜ | 🎁 output_fields · --filter-in 'pattern' [D:2/B:5/U:5 → Eff:2.5] 🎯 |
| Task 22 | ⬜ | 🎁 output_fields · --min-duration MS filter [D:2/B:4/U:4 → Eff:2.0] 🎯 |
| Task 15 | ✅ | 🎁 failed_iteration · Warn-by-default for --failed usage [D:3/B:9/U:9 → Eff:3.0] 🎯 |
| Task 16 | ✅ | 🎁 failed_iteration · Smart --failed hint [D:2/B:6/U:6 → Eff:3.0] 🎯 |
| Task 19 | ✅ | 🎁 error_grouping · --group-by-error [D:6/B:7/U:7 → Eff:1.17] 📋 |
| Task 23 | ⬜ | 🎁 error_grouping · Error type classification [D:5/B:6/U:6 → Eff:1.2] 📋 |
| Task 24 | ⬜ | 🎁 error_grouping · --group-by TAG option [D:6/B:6/U:6 → Eff:1.0] 📋 |
| Task 12 | ✅ | 🎁 infrastructure · Umbrella project support [D:3/B:7/U:7 → Eff:2.33] 🎯 |
| Task 14 | ✅ | 🎁 infrastructure · Code coverage with --cover [D:5/B:7/U:7 → Eff:1.4] 📋 |
| Task 25 | ⬜ | 🎁 future · --list flag for test discovery [D:3/B:5/U:5 → Eff:1.67] 🚀 |
| Task 26 | ⬜ | 🎁 future · Captured logs inclusion (--include-logs) [D:4/B:5/U:5 → Eff:1.25] 📋 |
| Task 27 | ⬜ | 🎁 future · CI systems integration [D:4/B:5/U:5 → Eff:1.25] 📋 |
| Task 28 | ⬜ | 🎁 future · Custom output templates [D:6/B:4/U:4 → Eff:0.67] |
| Task 29 | ⬜ | 🎁 future · Optional Jason fallback [D:4/B:5/U:5 → Eff:1.25] 📋 |
| Task 30 | ✅ | 🎁 failed_iteration · Automatic retry-on-flaky (default ON) [D:7/B:9/U:9 → Eff:1.29] 📋 |
- GenServer Formatter: Accumulate all results, output at end (buffered, not streaming)
- Separate Encoder: JSON encoding logic isolated for testability
- Application.get_env for Options: Pass options from Mix task to formatter
- Config Module: Centralize option parsing/validation in
ExUnitJSON.Config - Codec Boundary: Encoder returns plain maps/lists; actual JSON serialization only in formatter
- Primary: Use Elixir 1.18+'s built-in
:jsonmodule when available - Fallback: Use
Jasonif:jsonis unavailable (optional dependency) - ex_doc: Documentation only (dev dependency)
- Elixir 1.18+: Preferred (native
:json) - Earlier versions: Supported via optional Jason fallback
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| ExUnit formatter API changes | Low | Medium | Pin to stable API, test against multiple Elixir versions |
| Large test suites produce huge JSON | Medium | Low | Add --failures-only flag, consider streaming in Phase 2 |
| Non-serializable values in assertions | Medium | Medium | Robust inspect/1 fallback for all values |
| Users without Elixir 1.18 | Medium | Medium | Provide Jason fallback and document compatibility |
- Hex package vs Elixir core? → Hex package
- Streaming vs buffered JSON? → Buffered (with filtering options)
- Should we truncate very long assertion values? → Yes, with sensible, documented defaults
- Include captured logs by default or opt-in? → Opt-in (planned for Phase 2)
Root
- version: integer (1)
- seed: integer
- summary: object
- tests: array of test objects (omitted with
--summary-only; filtered with--failures-only) - error_groups: array of error group objects (only with
--group-by-error) - meta: object (optional; includes truncation settings)
Summary
- total: integer
- passed: integer
- failed: integer
- skipped: integer
- excluded: integer
- duration_us: integer (microseconds)
- result: string ("passed" | "failed")
Test
- name: string
- module: string
- file: string
- line: integer
- state: string ("passed" | "failed" | "skipped" | "excluded")
- duration_us: integer (microseconds)
- tags: object (filtered)
- failures: array of failure objects (only when failed)
Failure
- kind: string (e.g., "error", "exit", "throw", "assertion")
- message: string
- assertion: object (optional; for assertion errors)
- left: string (inspected/truncated)
- right: string (inspected/truncated)
- expr: string
- stacktrace: array of frames
Frame
- file: string
- line: integer
- module: string (optional)
- function: string (optional)
- arity: integer (optional)
- app: string (optional)
Metadata
- truncation: object
- value_char_limit: integer (default: 500)
- expr_char_limit: integer (default: 200)
- collection_item_limit: integer (default: 50)
- printable_limit: integer (default: 500)
Error Group (only with --group-by-error)
- pattern: string (first line of error message, max 200 chars)
- count: integer
- example: object
- name: string
- module: string
- file: string
- line: integer
Ordering
- Tests are sorted by
file, thenline, thennamefor determinism.
Versioning
- Breaking schema changes bump
versionand are noted in CHANGELOG.