Skip to content

Architecture

coverctl is built with Domain-Driven Design (DDD) principles and follows clean architecture patterns. This document describes the project structure and design decisions.

coverctl/
├── cmd/coverctl/ # CLI entry point
├── internal/
│ ├── domain/ # Core business logic
│ ├── application/ # Application services
│ ├── infrastructure/ # External adapters
│ └── cli/ # CLI parsing and output
├── docs/ # Documentation (this site)
├── schemas/ # JSON schemas
└── templates/ # Config templates

The domain layer contains the core business logic with no external dependencies.

Key components:

  • CoverageStat: Coverage statistics for a file
  • Policy: Coverage policy definitions
  • Domain: Domain configuration
  • Result: Coverage evaluation results
  • Evaluate(): Policy evaluation logic
domain/policy.go
type Policy struct {
Default DefaultPolicy
Domains []Domain
}
func Evaluate(policy Policy, coverage map[string]CoverageStat) Result {
// Pure business logic, no I/O
}

The application layer orchestrates use cases by coordinating domain logic and infrastructure.

Key services:

  • Check(): Run tests, evaluate coverage, enforce policy
  • RunOnly(): Generate coverage profile
  • Report(): Analyze existing profile
  • Watch(): Continuous coverage monitoring

Interfaces defined here:

  • CoverageRunner: Run tests with coverage
  • ProfileParser: Parse coverage profiles
  • ConfigLoader: Load configuration
  • Reporter: Format and output results
application/service.go
type Service struct {
CoverageRunner CoverageRunner
ProfileParser ProfileParser
ConfigLoader ConfigLoader
Reporter Reporter
// ...
}
func (s *Service) Check(ctx context.Context, opts CheckOptions) error {
// Orchestrate domain + infrastructure
}

Infrastructure Layer (internal/infrastructure/)

Section titled “Infrastructure Layer (internal/infrastructure/)”

Adapters for external systems and I/O operations.

Adapters:

  • gotool/: Go toolchain integration (go test)
  • config/: YAML configuration loading
  • coverprofile/: Coverage profile parsing
  • report/: Text/JSON/HTML output
  • diff/: Git diff integration
  • watcher/: File system watching
infrastructure/gotool/runner.go
type Runner struct {
Module ModuleResolver
Exec func(ctx, dir, args) error
}
func (r Runner) Run(ctx context.Context, opts RunOptions) (string, error) {
// Execute go test with coverage
}

Command-line interface parsing and user interaction.

Responsibilities:

  • Parse command-line arguments
  • Validate inputs
  • Call application services
  • Format output for terminal
cli/cli.go
func Run(args []string, stdout, stderr io.Writer, svc Service) int {
// Parse args, dispatch to service methods
}

All dependencies point inward toward the domain layer:

CLI → Application → Domain
Infrastructure

Application layer defines interfaces; infrastructure implements them.

The application core is isolated from external concerns:

┌─────────────────┐
│ CLI Port │
└────────┬────────┘
┌─────────────▼─────────────┐
│ Application Core │
│ ┌─────────────────────┐ │
│ │ Domain │ │
│ └─────────────────────┘ │
└─────────────┬─────────────┘
┌─────────────▼─────────────┐
│ Infrastructure Ports │
│ ┌──────┐ ┌──────┐ ┌────┐ │
│ │ Git │ │ File │ │ Go │ │
│ └──────┘ └──────┘ └────┘ │
└───────────────────────────┘

The domain layer has no side effects:

// Pure function - easy to test
func Evaluate(policy Policy, coverage Coverage) Result {
// No I/O, no state mutation
return result
}

Small, focused interfaces:

// Single responsibility
type CoverageRunner interface {
Run(ctx context.Context, opts RunOptions) (string, error)
}
type ProfileParser interface {
Parse(path string) (map[string]CoverageStat, error)
}

Each layer is tested in isolation:

  • Domain: Pure function tests
  • Application: Mock infrastructure interfaces
  • Infrastructure: Integration tests with real I/O
  • CLI: End-to-end argument parsing tests

coverctl uses itself for coverage enforcement:

.coverctl.yaml
policy:
default:
min: 75
domains:
- name: domain
match: ["./internal/domain/..."]
min: 85
- name: application
match: ["./internal/application/..."]
min: 80

Minimal external dependencies:

  • charmbracelet/bubbletea: TUI wizard
  • charmbracelet/lipgloss: Terminal styling
  • fsnotify/fsnotify: File watching
  • gopkg.in/yaml.v3: YAML parsing

Uses covermode=atomic for accurate concurrent coverage.

Standard Go coverage profile format for compatibility with go tool cover.