Coverage Workflow
SkillDev toolsUse when chasing missed lines/regions to satisfy CI's zero-missed-coverage gate, or writing tests to close uncovered ranges. Covers the coverage-missing.py workflow, coverage-friendly Rust idioms, and the common uncovered hotspots.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Coverage Workflow skill
What this skill tells your AI
The instructions your AI receives, as published by owenlamont/ryl in .agents/skills/coverage/SKILL.md and read by ahel’s review.
The CI enforces zero missed lines and zero missed regions. Use this workflow instead of hunting through scattered tips:
- First run
prek run --all-filesand rerun it until all automatic fixes have stabilised and a full pass succeeds without modifying files. - Quick status before pushing: run
uv run .agents/skills/coverage/coverage-missing.py(cross-platform; needsuv+ a Rust toolchain withcargo-llvm-cov). It reruns the coverage suite and prints any uncovered ranges, or explicitly confirms when coverage is complete. - If the coverage script itself fails, it prints the failing nextest output (the FAIL
lines + panic tails); fix those tests, then rerun. Fall back to a manual
cargo nextest runonly when you need the full log. - If the script reports files, extend CLI/system tests targeting those ranges until the script produces no output.
- For richer artifacts (HTML/LCOV), see the cargo-llvm-cov docs (HTML isn't easily machine-readable).
- When coverage points to tricky regions, prefer CLI/system tests in
tests/that driveenv!("CARGO_BIN_EXE_ryl")so you exercise the same paths as users. - When you need to observe the exact flow through an uncovered branch, run the
failing test under
rust-lldb(ships with the toolchain). Start withcargo test --no-runand thenrust-lldb target/debug/deps/<test-binary> -- <filter args>to set breakpoints on the problematic lines. - If cached coverage lingers, clear
target/llvm-cov-targetand rerun. But a single stubborn uncovered region is usually an unreachable branch, not stale cache — before clearing, confirm the missed line is reachable by some input. An unhitif letelse / error arm looks identical to a cache miss but is closed by a test (or anexpect), not a rebuild; chasing the cache first has burned multiple rebuilds.
Coverage-Friendly Rust Idioms
- Guard invariants with
expect(or an earlyreturn Err(...)) when the “else” branch is truly unreachable. Leaving areturnin the unreachable path often shows up as a permanent uncovered region even though the condition is ruled out. Reserveassert!for test-only code or cases where a runtime panic is acceptable. - When walking indices backwards, call
checked_sub(1).expect("…")instead of matching onchecked_sub; theexpectdocuments the invariant and removes the uncoveredNonebranch that instrumentation reports. - When collecting spans, store the raw
(start, end)and filter once at the end rather than pushingRangeconditionally, so LLVM records the conversion branch once. - Normalize prefix checks with
strip_prefix(...).expect(...)when the prefix is already guaranteed; this removes the otherwise-uncoveredreturnpath.
Windows/MSVC: ensure the llvm-tools-preview component is installed (already listed in
rust-toolchain.toml). Run from a Developer Command Prompt if linker tools go missing.
Common hotspots
- A CLI/system test that sets a subprocess working dir via
Command::current_dircan stop the instrumented child writing its.profraw(so its lines read as uncovered). Drive coverage-sensitive dedup/path tests with absolute paths instead of changing cwd. - Configuration discovery: use the
Envabstraction (discover_config_with) and fake envs to hit inline data, explicit files (success and YAML failure), and env-var paths. - Project configuration search: cover empty inputs, single files without parents, and multiple files in the same directory to trigger dedup logic.
- YAML parsing: drive
from_yaml_strthrough string vs sequence options and ensure rule merges hit both update and insert branches. - CLI context resolution: pass an empty
PathBufintoresolve_ctxto trigger the fallback to.. - Flow scanners in rules: always reconcile parser byte spans with
char_indices()viacrate::rules::span_utilsto avoid off-by-byte bugs when UTF-8 characters appear. - Rules using the shared
crate::rules::support::mapping_key_walker::Walkerto track key/value position must advance it for every node-producing event, includingEvent::Alias(callWalker::skip_node). An alias in value position (k: *a, or a<<: *basemerge) that does not advance the walker desyncs the key/value alternation, so the following key is read as a value and vice-versa. Exercise rules with aliases in both key and value position. - Resolving a scalar to its typed value (int/bool/null/float/string) is centralised in
crate::yaml_dom::scalar(resolve_scalar/resolve_plain_scalar); reuse it rather than reinventing parsing. ryl resolves scalars per the YAML 1.2 core schema everywhere (leading-zero decimal is an int, an empty plain scalar is null,0x/0oradixes, full bool/null spelling sets); keep that schema choice consistent across rules instead of switching to JSON/1.1 semantics in any single rule. - Matching a core-schema tag (
!!int,!!str, …): usecrate::yaml_dom::core_schema_suffix(tag)/is_core_schema(tag), never granit'sTag::is_yaml_core_schema(it inspects only the handle, so a verbatim core tag!<tag:yaml.org,2002:int>slips past it). The shared helpers handle the canonical handle (incl. a resolving%TAG) and the verbatim spelling, but not a%TAGthat splits the URI mid-token; to match one type regardless of split point, compare the full resolved URI (handle++suffix), asrules::support::merge_keydoes.
Signals
- GitHub stars
- 72
- Forks
- 3
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
coverage-owenlamont- Source
- github.com/owenlamont/ryl