coverage-cat
v1.0.0
Published
LLM-native coverage-annotated file viewer. Renders LNCAF format for AI-readable code coverage.
Maintainers
Readme
covcat
LLM-native coverage-annotated file viewer.
covcat reads a source file and overlays code coverage data from your test suite, producing output in LNCAF (LLM-Native Coverage Annotation Format) --- a text format designed for AI consumption, not human IDE gutters. Every executable line gets a coverage gutter marker. An LLM reading covcat output goes from coverage-blind to coverage-aware in a single tool call.
/* COV: src/orders.ts | | covered . uncovered ? partial [B:x/y]=branches [FN+/-]=function
Collected: 2026-04-01 | lines: 5/12 (42%) | branches: 1/4 (25%)
*/
[FN+] 1->function processOrder(order: Order): Result {
| 2-> const validated = validateOrder(order);
? 3-> if (!validated) { [branch: else never taken]
. 4-> throw new ValidationError('Invalid order');
. 5-> }
| 6-> const pricing = calculatePrice(order.items);
? 7-> if (pricing.discount > 0) { [branch: both paths uncovered]
. 8-> applyDiscount(pricing);
. 9-> } else {
. 10-> pricing.discount = 0;
. 11-> }
| 12-> return fulfillOrder(order, pricing);
13->}An LLM reading this immediately knows: line 4 is untested (.), line 3 has a partial branch (?) where the else path is never taken, and lines 8--11 have zero coverage. Instead of guessing what needs testing, it can write "a test where validateOrder returns false" --- a concrete, actionable instruction derived from the annotation.
Table of Contents
- Installation
- Quick Start
- LNCAF Format
- CLI Reference
- Daemon Mode
- Configuration
- Coverage Formats
- Architecture
- Claude SKILL Integration
- Development
- Research Background
- License
Installation
# Clone and install
git clone <repo-url> covcat && cd covcat
yarn install
# Build
yarn build
# Link globally (optional)
npm linkRequires Node.js 18+ and Yarn 4.
Quick Start
# 1. Run your tests with coverage first
yarn test:coverage # or: npx vitest --coverage
# 2. View coverage-annotated source
npx covcat src/utils.ts
# 3. That's it. covcat auto-discovers coverage data.If you have coverage/coverage-final.json (Jest/Vitest) or coverage/lcov.info anywhere in your project tree, covcat finds it automatically.
LNCAF Format
LNCAF defines a single-character left-margin gutter on every executable line, with optional directional branch suffixes. Three tiers trade detail for token efficiency.
Gutter Vocabulary
| Marker | Meaning |
|--------|---------|
| \| | Line covered (executed at least once) |
| . | Line uncovered (0 executions) |
| ? | Branch statement, at least one path untaken |
| | Non-executable line (blank, comment, closing brace) |
| [FN+] | Function entry, called |
| [FN-] | Function entry, never called |
Compact Tier (~1.2 tokens/line)
Minimal. Single-char gutter only. Best for large files (800+ lines) or batch scans.
// cov: | covered . uncovered ? partial branch
| 1->export function add(a, b) {
| 2-> return a + b;
3->}Standard Tier (~1.6 tokens/line) --- default
Gutter + function markers + directional branch suffixes. Best for interactive agentic workflows and test generation.
/* COV: src/math.ts | | covered . uncovered ? partial [B:x/y]=branches [FN+/-]=function
Collected: 2026-04-01 | lines: 6/8 (75%) | branches: 1/2 (50%)
*/
[FN+] 1->export function divide(a: number, b: number): number {
? 2-> if (b === 0) { [branch: else never taken]
. 3-> return 0;
. 4-> }
| 5-> return a / b;
6->}Verbose Tier (~5-7 tokens/line)
Hit counts + function-level gap summary. Best for single-function deep analysis or hot-path review.
/* COV: src/orders.ts | ...
Top gaps: L4-5, L10-11
*/
[FN+:42] 1->function processOrder(order: Order): Result {
|:42 2-> const validated = validateOrder(order);
? 3-> if (!validated) { [branch: if-true never taken]
. 4-> throw new ValidationError('Invalid order');Directional Branch Encoding
The single highest-value feature. Instead of a bare ratio [B:1/2], covcat tells the LLM which branch to test:
| Pattern | Annotation |
|---------|-----------|
| if/else --- only if-true taken | [branch: else never taken] |
| if/else --- only else taken | [branch: if-true never taken] |
| switch --- 2 of 4 cases hit | [branch: case 2 never taken, case 3 never taken] |
| x ?? default --- non-null only | [branch: alternate never taken] |
| All branches missed | [branch: both paths uncovered] |
When directional data is unavailable (e.g., LCOV), falls back to ratio: [branch: 1/2 taken].
CLI Reference
Usage: covcat [options] [command] [file]
LLM-native coverage-annotated file viewer. Renders LNCAF format.
Arguments:
file Source file to annotate with coverage
Options:
-V, --version Output the version number
-f, --format <tier> Annotation tier: compact, standard, verbose
-c, --coverage <path> Explicit coverage file path
--no-branches Hide branch detail annotations
--hit-counts Show hit counts (verbose tier)
--no-daemon Skip daemon, always parse directly
-h, --help Display help
Commands:
daemon Manage the covcat daemonExamples
# Auto-discover coverage, standard tier
covcat src/utils.ts
# Compact tier for a large file
covcat --format compact src/large-module.ts
# Verbose with hit counts
covcat --format verbose --hit-counts src/hot-path.ts
# Explicit coverage file
covcat --coverage coverage/lcov.info src/utils.ts
# Skip daemon, parse directly
covcat --no-daemon src/utils.tsDaemon Mode
For fast repeated evaluation, covcat includes a background daemon that caches parsed coverage data and watches for changes.
# Start the daemon
covcat daemon start
# All subsequent covcat calls go through the daemon (<5ms cache hits)
covcat src/utils.ts # First call: parse + cache
covcat src/utils.ts # Second call: cache hit
# Check status
covcat daemon status
# covcat daemon: running
# PID: 12345
# Cache size: 3 entries
# Memory: 1.2 MB
# Watched: 1 files
# Stop when done
covcat daemon stopHow It Works
- The daemon listens on a Unix domain socket (
.covcat/daemon.sock) - When
covcat <file>runs, it checks if the daemon is available - If running: sends an annotate request over the socket, gets cached results
- If not running: falls back to direct pipeline (parse inline)
- The daemon watches coverage files with
fs.watchand invalidates cache on change
Daemon Architecture
CLI ──> Unix Socket ──> DaemonServer
│
┌─────┴──────┐
│ LRU Cache │ (500 entries, 128MB)
│ │
│ fs.watch │ (auto-invalidation)
└────────────┘Run in foreground for debugging:
covcat daemon start --foregroundConfiguration
Create covcat.yml (or .covcat.yml, covcat.yaml) in your project root:
coverage:
# Annotation tier: compact | standard | verbose
defaultFormat: standard
# Show covered lines with | marker
showCoveredLines: true
# Show execution hit counts (verbose tier)
showHitCounts: false
# Show directional branch details
showBranchDetails: true
# Max token budget for annotations (0 = unlimited)
maxAnnotationTokensBudget: 0
# Staleness handling: warn-and-show | warn-and-hide | silent-hide | error
stalenessMode: warn-and-show
# Staleness check: mtime | linecount | hash
stalenessCheckLevel: mtime
# Coverage file discovery
discovery:
maxWalkDepth: 10
additionalCandidates: []
# Explicit path (overrides auto-discovery):
# coveragePath: coverage/lcov.info
# Daemon settings
daemon:
enabled: true
watchFiles: true
cache:
maxEntries: 500
maxMemoryMB: 128Resolution Order
Settings resolve in order of precedence:
- CLI flags (
--format verbose) --- highest priority - covcat.yml (
defaultFormat: compact) - Built-in defaults (
standard)
Config Discovery
covcat walks up from the source file's directory looking for config files, checking each of:
covcat.ymlcovcat.yaml.covcat.yml.covcat.yaml
First match wins.
Coverage Formats
covcat auto-discovers and parses coverage data. Format detection uses file extension + content sniffing (first 512 bytes).
Supported Formats
| Format | Files | Ecosystem | Branch Quality |
|--------|-------|-----------|---------------|
| Istanbul JSON | coverage-final.json | Jest, Vitest, NYC, c8 | Directional (if/switch/cond-expr) |
| LCOV | lcov.info, *.lcov | Universal interchange | Ratio-only (opaque block IDs) |
Auto-Discovery Probe Order
covcat walks up to the project root (detected via package.json, go.mod, Cargo.toml, etc.) and probes:
coverage/coverage-final.json # Jest/Vitest default
coverage/coverage-summary.json # NYC default
.nyc_output/coverage-final.json
coverage/lcov.info
lcov.info16 paths are checked across Istanbul, LCOV, Cobertura, JaCoCo, Go, llvm-cov, SimpleCov, and coverage.py ecosystems.
Adapter Interface
Adding a new format is one file implementing CoverageAdapter:
interface CoverageAdapter {
readonly format: CoverageFormat;
canParse(filePath: string, contentPreview: string): boolean;
parse(coverageFilePath: string, content: string): Promise<FileCoverageMap>;
}Register it in src/adapters/detect.ts and coverage for that format works automatically.
Architecture
┌──────────────────────────────────────────────────────┐
│ CLI (commander) │
│ covcat <file> ─── daemon subcommands │
└──────────┬──────────────────────────────┬────────────┘
│ direct │ via socket
v v
┌──────────────────┐ ┌─────────────────────┐
│ Pipeline │ │ DaemonServer │
│ │ │ │
│ discovery │ │ LRU Cache (500) │
│ detect format │ │ fs.watch │
│ parse adapter │ │ Unix socket IPC │
│ render LNCAF │ │ │
└──────────────────┘ └─────────────────────┘
│ │
v v
┌──────────────────────────────────────────────────────┐
│ Adapters │
│ IstanbulAdapter LcovAdapter (pluggable) │
│ │ │ │
│ v v │
│ ┌─────────────────────────────┐ │
│ │ Unified Data Model │ │
│ │ LineCoverage, BranchData, │ │
│ │ FunctionEntry, FileSummary │ │
│ └─────────────────────────────┘ │
└──────────────────────────────────────────────────────┘
│
v
┌──────────────────────────────────────────────────────┐
│ LNCAF Renderer │
│ Compact ──── Standard ──────── Verbose │
│ (|.? ) (+[FN+] +branch) (+hits +gaps) │
└──────────────────────────────────────────────────────┘Key Design Decisions
| Decision | Choice | Why |
|----------|--------|-----|
| Annotation placement | Left-margin prefix | LLM attention primacy: prefix primes interpretation |
| Branch encoding | Directional text | "else never taken" > "1/2" for test generation |
| Covered line annotation | Single \| char | Asymmetric density: 1 token for covered, 5-8 for uncovered |
| Daemon IPC | Unix domain socket | Lowest latency, no port conflicts, automatic cleanup |
| Cache strategy | LRU with mtime staleness | Parse-once serve-many; sub-ms cache hits |
| Config validation | Zod schema | Type-safe, descriptive errors on invalid values |
Source Layout
src/
index.ts CLI entry point (commander)
cli/
pipeline.ts Annotation pipeline orchestrator
core/
types.ts LNCAF type system (32 types)
config.ts YAML config loader (zod)
discovery.ts Coverage file auto-discovery
adapters/
istanbul.ts Istanbul JSON parser
lcov.ts LCOV tracefile parser
detect.ts Format detection + adapter registry
renderer/
renderer.ts 3-tier LNCAF renderer
daemon/
cache.ts LRU coverage cache
server.ts Unix socket daemon + clientClaude SKILL Integration
covcat ships with a Claude SKILL definition at .claude/skills/covcat/SKILL.md. When installed in a Claude Code project, it enables natural-language triggers:
- "show coverage for src/utils.ts"
- "what's untested in this file?"
- "coverage gaps in the auth module"
- "run covcat on the changed files"
The SKILL definition includes the full LNCAF gutter reference, usage examples, and configuration guidance so Claude can use covcat effectively without additional prompting.
Development
# Install dependencies
yarn install
# Build TypeScript
yarn build
# Run tests
yarn test
# Run tests with coverage
yarn test:coverage
# Coverage fitness score (composite metric)
./scripts/score.sh
# Run in dev mode (no build needed)
npx tsx src/index.ts --help
npx tsx src/index.ts --coverage test/fixtures/coverage-final.json test/fixtures/utils.tsTesting
Tests use Vitest with real fixture data (no mocking of coverage adapters):
test/
adapters/
istanbul.test.ts Istanbul JSON parsing + branch descriptions
lcov.test.ts LCOV parsing + summary computation
renderer/
renderer.test.ts All 3 tiers + gutter markers + branch suffixes
fixtures/
coverage-final.json Istanbul JSON fixture (processOrder function)
sample.lcov LCOV fixture (add + divide functions)
utils.ts Source file matching Istanbul fixture
math.ts Source file matching LCOV fixtureCoverage Fitness Function
The project uses a weighted composite score to track test coverage:
score = stmts_pct * 0.60 + branches_pct * 0.25 + fns_pct * 0.15./scripts/score.sh # Human-readable
./scripts/score.sh --json # Machine-readableResearch Background
covcat is built on the LNCAF research corpus, a systematic analysis of LLM-native coverage representation:
- 13 source coverage formats analyzed (LCOV, Istanbul, Cobertura, JaCoCo, Go, llvm-cov, SimpleCov, coverage.py, Codecov, V8, OpenCover, Clover, dotCover)
- 17 AI-coverage tools surveyed (CoverUp, Qodo Cover, Codecov AI, SonarQube, GitHub Copilot, etc.)
- 7 annotation strategies evaluated on an 8-criterion weighted matrix
- 0 existing tools produce coverage-annotated output in the line-numbered text format LLMs read
The key finding: no existing tool fills the gap between IDE gutter markers (visual, invisible to LLMs) and raw coverage data files (separate from source, require mental mapping). LNCAF bridges this gap with inline annotation that adds ~800 tokens of overhead to a 500-line file --- a 5-7% increase that transforms an LLM from coverage-blind to coverage-aware.
CoverUp (arXiv:2403.16218) provides the empirical proof: inline line-tagging achieves 82% median coverage vs. 47% baseline. Preamble-only references fail. covcat extends this insight from excerpt-level to file-level operation.
See FINAL-SYNTHESIS.md for the complete specification.
License
MIT
