# cargo-coverage-gate — Implementation Plan 0000
> Tracks the initial implementation of the design in
> `docs/design/main.md`. Update this document as work lands; keep
> commit hashes against finished items so the trail through the git
> log is recoverable.
>
> Status: **Draft**.
## 1. Scope
Deliver a v1 of `cargo-coverage-gate` that matches the design:
- A single command (no subcommands): `cargo coverage-gate` gates.
The tool is read-only.
- Reads cargo-llvm-cov lcov tracefiles.
- Three-layer threshold resolution: per-crate → workspace →
built-in `100.0`.
- Text and Markdown output; auto-detected step-summary file on
GitHub Actions; explicit `--summary-file` everywhere else.
- Exit codes `0` / `1` / `2` per the design.
- Loud-failure on zero data for a gated crate.
Out of scope for this plan: function/region thresholds, cobertura
input, per-file thresholds, baseline-trend reporting. These are
called out in §11 of the design and stay deferred.
## 2. Crate layout
```
crates/cargo-coverage-gate/
├── Cargo.toml
├── README.md # auto-generated by `just readme`
├── docs/
│ ├── design/main.md
│ └── implementation-plans/0000.md
└── src/
├── lib.rs # library surface (used by integration tests)
├── bin/
│ └── cargo-coverage-gate/
│ ├── main.rs # cargo subcommand entry point
│ └── cli.rs # clap definitions, argument parsing
├── error.rs # crate-local error type
├── lcov_cov.rs # lcov tracefile parser (wraps `lcov` crate)
├── workspace.rs # workspace discovery, member enumeration
├── threshold.rs # three-layer resolution, metadata schema
├── attribute.rs # file-to-crate longest-prefix matching
├── aggregate.rs # per-crate line totals
├── verdict.rs # compare measured vs threshold; exit code
└── render/
├── mod.rs
├── text.rs # stdout table
└── markdown.rs # summary-file table
```
Mirrors the `cargo-heather` shape (binary under `src/bin/<name>/`,
library at `src/lib.rs`, integration tests under `tests/`).
## 3. Dependencies
All declared in `[workspace.dependencies]` with `default-features =
false` per workspace policy. Reuse existing entries where possible.
| `clap` | CLI parsing (derive) |
| `cargo_metadata` | Workspace + member discovery |
| `lcov` | cargo-llvm-cov lcov tracefile parsing |
| `ohno` | Library error type (`#[ohno::error]`) |
| `serde_json` | Reading `[package.metadata.coverage-gate]` blocks |
| `tempfile` | Integration test scaffolding (dev) |
| `assert_cmd` | CLI integration tests (dev) |
| `predicates` | Output assertions (dev) |
The tool is read-only, so `toml_edit` is not needed.
## 4. Phased work
Phases are sized so each lands as one PR. Each phase ends with green
`just check` (build + clippy + tests) on the affected crate.
### Phase 1 — Skeleton and CI plumbing
- Add `crates/cargo-coverage-gate/Cargo.toml` with workspace inherit
blocks (`edition`, `rust-version`, `authors`, `license`,
`homepage`, `lints`) matching `cargo-heather`.
- `src/lib.rs` exposing a placeholder `pub fn run(...) -> ...`.
- `src/bin/cargo-coverage-gate/main.rs` with a clap skeleton that
parses the top-level flags and returns "not implemented". Handle
the cargo-subcommand convention: when invoked as `cargo
coverage-gate ...`, cargo passes `coverage-gate` as `argv[1]`; the
binary must accept and skip that token before parsing flags.
- Add the crate to the root `Cargo.toml` `[workspace.dependencies]`
block.
- Wire `just readme` to generate `README.md` from `lib.rs` docs.
Exit: crate builds, binary runs `--help`, `just check` passes.
### Phase 2 — lcov model
- Module `lcov_cov.rs` wrapping the `lcov` crate. Flattens
`lcov::Report` into a `CoverageReport { files: Vec<FileEntry> }`
where `FileEntry { filename, lines_total, lines_covered }` is
computed from each `SF:` section's `DA:` records.
- Unit tests with golden fixtures stored in
`tests/fixtures/lcov/*.info`. Cover: empty file, single source
section, multi-source section, mixed-coverage, malformed input.
Exit: deserialization round-trips fixtures; warnings exercised in
tests.
### Phase 3 — Workspace discovery + member enumeration
- Module `workspace.rs` wrapping `cargo_metadata` to:
- Locate the workspace root from CWD.
- List workspace members using the manifest directories as reported
by `cargo_metadata` (no further canonicalization).
- Read each member's `[package.metadata.coverage-gate]` and the
root's `[workspace.metadata.coverage-gate]`.
- Unit tests with `tempfile` fixtures: workspace with no metadata,
with workspace-level default, with mixed per-crate overrides.
### Phase 4 — Threshold resolution
- Module `threshold.rs` implementing the three-layer rule:
per-crate → workspace → `100.0`. Record the resolution source
(`Crate` / `Workspace` / `Default`) for the verdict table's
`Source` column.
- Schema: accept `min-lines-percent` as either integer or float TOML value
via a custom deserializer; range-validate `[0.0, 100.0]`.
### Phase 5 — Attribution + aggregation + verdict
- `attribute.rs`: longest-prefix match of file paths against member
manifest directories as reported by `cargo_metadata`; drop unmatched
files with a single aggregated warning.
- `aggregate.rs`: sum `lines.count` and `lines.covered` per crate;
order-independent (integer summation, no pre-sort required).
- `verdict.rs`: for each gated crate, compare measured vs threshold
after rounding both sides to one decimal place (matching the
displayed precision, so the rendered Δ always agrees with the
verdict — see design §10.5); classify as `Ok` / `Fail` / `NoData`;
derive process exit code (`0` all-ok, `1` any-fail, `2`
any-no-data when that crate was in the gated set).
Exit: end-to-end library call `run(lcov_text, workspace) -> Verdict`
working with fixtures; full unit-test coverage.
### Phase 6 — Renderers
- `render/text.rs`: deterministic stdout table matching §5.4 of the
design (columns, separators, totals line, `Source` column).
- `render/markdown.rs`: GFM table matching §6.5, leading
`### coverage-gate` header.
- `--summary-file` writes the Markdown output. When unset, the tool
detects `GITHUB_STEP_SUMMARY` and `COVERAGE_GATE_SUMMARY` in that
order and writes there.
- `--quiet` suppresses stdout but never the summary file.
- Unit tests assert renderer output via direct string assertions
(substring checks against expected column values and summary lines).
### Phase 7 — End-to-end CLI tests
- `tests/cli.rs` modelled on `cargo-heather`'s. For each scenario:
build a temp workspace, drop a fixture lcov tracefile, run the binary via
`assert_cmd`, assert exit code + table output.
- Scenarios to cover:
- all-pass with mixed sources (package / workspace / default)
- one package below threshold (exit 1)
- one gated package with no data (exit 2)
- `--package` (`-p`) restricting scope (literal name, glob pattern,
and repeated form all covered)
- `--summary-file` written; `GITHUB_STEP_SUMMARY` auto-detected
- lcov with TN:test_name records parses cleanly
- Malformed lcov exits 2 with a clear message
### Phase 8 — Documentation and release prep
- Crate-level rustdoc covering: public library surface, minimal
example, links to `docs/design/main.md`.
- Regenerate `README.md` via `just readme`.
- Add the crate to the workspace `cargo publish` story if there's a
release script (mirror what `cargo-heather` does — verify before
copying).
- Update root `README.md` to list the new crate.
- Add any words triggered by the spellchecker to `.spelling`.
## 5. Risks and open decisions
1. **`cargo_metadata` cost.** Calls `cargo` on the user's
workspace, which is slow on large repos. Acceptable for CI; for
local repeated runs, consider caching the resolved member list
keyed on workspace root mtime. Defer to a follow-up if it bites.
2. **Workspace-default merge semantics.** Decision: per-crate `min-lines-percent` fully replaces the workspace value (no field-level
merge, since the schema has one field). Document if/when more
fields land.
3. **CI integration in this repo.** Out of scope for the
implementation plan, but worth opening a follow-up issue once the
crate is publishable: dogfood the gate on `ox-tools` itself.
## 6. Tracking
Each phase becomes one TODO under `todo/` once work starts, with the
PR linked back here on completion. Leave items here as the canonical
narrative; treat TODOs as the day-to-day work queue.