Architecture
DotnetTokenKiller follows a clean architecture pattern with four layers, each in its own project.
Project Structure
src/
├── DotnetTokenKiller.Domain/ Core abstractions
├── DotnetTokenKiller.Application/ Use cases and filters
├── DotnetTokenKiller.Infrastructure/ External integrations
└── DotnetTokenKiller.Cli/ CLI entry point
Layer Diagram
graph TD
CLI["CLI<br/><small>Spectre.Console — Commands, Settings, DI setup</small>"]
APP["Application<br/><small>Use cases, Output filters</small>"]
INFRA["Infrastructure<br/><small>SQLite, JSON config, Process</small>"]
DOM["Domain<br/><small>Interfaces, Models, Value objects</small>"]
CLI --> APP
CLI --> INFRA
APP --> DOM
INFRA --> DOM
Dependencies flow inward: CLI → Application → Domain, and Infrastructure → Domain. The CLI layer wires everything together via dependency injection.
Domain
The innermost layer defines core abstractions with no external dependencies.
| Folder | Contents |
|---|---|
Configuration/ |
DtkConfig (configuration model), IConfigProvider (config loading interface) |
Execution/ |
CommandResult (return value from running a command), ICommandRunner (process execution interface) |
Filters/ |
IOutputFilter (contract for transforming raw output to filtered output) |
Tracking/ |
CommandRecord, CommandGainDetail, GainSummary (tracking models), ITracker (persistence interface) |
Tee/ |
ITeeService (raw output logging interface) |
Application
Contains the business logic — use cases and per-command output filters.
Use Cases
| Class | Purpose |
|---|---|
FilteredRunUseCase |
Runs a dotnet command, filters its output, tracks token usage, and optionally tees the raw output |
GainReportUseCase |
Aggregates tracking data and produces the savings report |
ResetTrackingUseCase |
Clears all stored tracking records |
Filters
Each supported dotnet subcommand has a dedicated filter implementing IOutputFilter:
| Filter | Command |
|---|---|
DotnetBuildFilter |
dotnet build |
DotnetTestFilter |
dotnet test |
DotnetRestoreFilter |
dotnet restore |
DotnetCleanFilter |
dotnet clean |
DotnetFormatFilter |
dotnet format |
These filters parse the raw dotnet output and produce compact, LLM-friendly summaries — stripping MSBuild noise, adapter banners, absolute paths, and duplicate information.
Infrastructure
Implements domain interfaces with concrete external integrations.
| Class | Implements | Description |
|---|---|---|
ProcessCommandRunner |
ICommandRunner |
Spawns dotnet as a child process and captures output |
SqliteTracker |
ITracker |
Persists command records and token counts in SQLite |
JsonConfigProvider |
IConfigProvider |
Reads and deserializes ~/.config/dtk/config.json |
CLI
The entry point, built with Spectre.Console.Cli.
| Component | Purpose |
|---|---|
Program.cs |
Registers services, configures commands, handles passthrough for unknown subcommands |
Commands/ |
One command class per CLI verb (DotnetBuildCommand, DotnetTestCommand, GainCommand, ResetCommand, etc.) |
Infrastructure/ |
Spectre.Console DI integration (TypeRegistrar, TypeResolver) |
Key Dependencies
| Package | Purpose |
|---|---|
| Spectre.Console | Rich terminal output (tables, colors, emoji) |
| Spectre.Console.Cli | CLI command framework |
| Microsoft.ML.Tokenizers | BPE token counting (cl100k_base, o200k_base, etc.) |
| Microsoft.Data.Sqlite | Tracking data persistence |
| Microsoft.Extensions.DependencyInjection | Service container |
Target Framework
- .NET 10 (
net10.0) - C# 14 with nullable reference types enabled
- Analyzers: Roslynator, SonarAnalyzer, Microsoft.CodeAnalysis.NetAnalyzers
TreatWarningsAsErrorsenabled