DnRelay 1.0.0
dotnet tool install --global DnRelay --version 1.0.0
dotnet new tool-manifest
dotnet tool install --local DnRelay --version 1.0.0
#tool dotnet:?package=DnRelay&version=1.0.0
nuke :add-package DnRelay --version 1.0.0
DnRelay
dnrelay is a compact wrapper around dotnet for AI agents and humans.
It keeps the full child output in repo-local logs, but prints a short stable summary by default. It also adds repo-aware locking, timeout control, environment injection, and tighter handling for noisy commands like tests and benchmarks.
By default, dnrelay asks the .NET CLI to emit English UI text via DOTNET_CLI_UI_LANGUAGE=en-US so summaries and saved logs stay stable across machines and operating systems. If you need a different language, override it with --env DOTNET_CLI_UI_LANGUAGE=<value>.
Additional guidance:
- generic usage guidance: BEST_PRACTICES.md
- repo-specific dogfooding notes: DOGFOODING.md
Status
Current commands:
buildtestrunbenchstatskillconfig
Companion development tool:
dnrelay-tool
Current design goals:
- short default output
- full logs on disk
- explicit timeout control
--envand--env-file- conservative locking where project metadata makes conflicts inferable
- practical dogfooding as a local .NET tool
- NativeAOT-friendly implementation choices
NativeAOT
The current implementation is written to stay friendly to NativeAOT:
- regex uses
GeneratedRegexAttribute - JSON output and lock metadata serialization use source-generated
System.Text.Jsonmetadata
Current validation command:
dotnet publish src\DnRelay\DnRelay.csproj -c Release -r win-x64 /p:PublishAot=true
Install
Install dnrelay as a global .NET tool:
dotnet tool install --global DnRelay
After global install, use the short command directly:
dnrelay --help
NuGet package:
Update:
dotnet tool update --global DnRelay
Uninstall:
dotnet tool uninstall --global DnRelay
For repository-local pinning instead of global install:
dotnet new tool-manifest
dotnet tool install --local DnRelay
dotnet dnrelay --help
Companion Tool
dnrelay-tool is a separate development helper, not part of the main execution wrapper surface.
Current companion command:
tool-refresh
Install:
dotnet tool install --global DnRelay.Tool
Package:
Example:
dnrelay-tool tool-refresh
dnrelay-tool tool-refresh --bump minor
dnrelay-tool tool-refresh --version 2.0.0-rc.1
dnrelay-tool tool-refresh src\DnRelay.Tool\DnRelay.Tool.csproj
tool-refresh:
- reads version from either
<Version>or<VersionPrefix>plus optional<VersionSuffix> - defaults to
patch, or uses<ToolRefreshBump>when present in the target project - accepts
--bump patch|minor|major - accepts
--version <semver>for an explicit target version - updates the local tool if it is already installed, or installs it into the local manifest if not
Commands
config
dnrelay config logs .artifacts\logs
dnrelay config logs .artifacts\logs --force
config logs writes .dnrelay\config.json with a logsDir entry for the current repository.
build
dnrelay build src\App\App.csproj
dnrelay build --target src\App\App.csproj
dnrelay build src\App\App.csproj --timeout 30s
dnrelay build src\App.slnx --json
dnrelay build src\App\App.csproj --env ContinuousIntegrationBuild=true
dnrelay build src\App\App.csproj --env-file .env.build
dnrelay build src\App\App.csproj --logs-dir .artifacts\logs
dnrelay build src\App\App.csproj -- --no-restore
dnrelay build src\App\App.csproj -- -c Release -p:TreatWarningsAsErrors=true
dnrelay build samples\DnRelay.BuildWarningSmokeApp\DnRelay.BuildWarningSmokeApp.csproj
dnrelay build samples\DnRelay.BuildErrorSmokeApp\DnRelay.BuildErrorSmokeApp.csproj
Summary includes:
- status
- target
- duration
- warning count
- error count
- log path
- lock scope summary
- lock wait duration
Text mode also prints an immediate BUILD STARTED block with the target and log path before lock wait or execution begins.
Build smoke samples in this repo:
samples\DnRelay.BuildWarningSmokeApp\DnRelay.BuildWarningSmokeApp.csproj- succeeds with a nullable warning so
warnings:andtop warnings:can be dogfooded
- succeeds with a nullable warning so
samples\DnRelay.BuildErrorSmokeApp\DnRelay.BuildErrorSmokeApp.csproj- fails with a compile error so
errors:andtop errors:can be dogfooded
- fails with a compile error so
test
dnrelay test tests\App.Tests\App.Tests.csproj
dnrelay test --target tests\App.Tests\App.Tests.csproj
dnrelay test tests\App.Tests\App.Tests.csproj --env ASPNETCORE_ENVIRONMENT=Development
dnrelay test tests\App.Tests\App.Tests.csproj --env-file .env.test
dnrelay test tests\App.Tests\App.Tests.csproj --logs-dir .artifacts\logs
dnrelay test tests\App.Tests\App.Tests.csproj --timeout 2m
dnrelay test tests\App.Tests\App.Tests.csproj --json
dnrelay test tests\App.Tests\App.Tests.csproj -- --filter MyTest
dnrelay test tests\App.Tests\App.Tests.csproj -- --filter Category=Smoke
dnrelay test tests\App.Tests\App.Tests.csproj -- --logger "console;verbosity=minimal"
test writes a full log and a .trx file, then summarizes:
- total
- passed
- failed
- skipped
- errors
- top failures
- log path
- trx path
- lock scope summary
- lock wait duration
Text mode also prints an immediate TEST STARTED block with the target and log path before lock wait or execution begins.
run
dnrelay run --project src\App\App.csproj
dnrelay run --target src\App\App.csproj
dnrelay run --project src\App\App.csproj --timeout 45s
dnrelay run --project src\App\App.csproj --logs-dir .artifacts\logs
dnrelay run --project src\App\App.csproj --env-file .env.local
dnrelay run --project src\App\App.csproj --env ASPNETCORE_ENVIRONMENT=Development
dnrelay run --project src\App\App.csproj --json
dnrelay run --project src\App\App.csproj --no-build
dnrelay run --project src\App\App.csproj --raw
dnrelay run --project src\App\App.csproj -- --urls http://localhost:5007
dnrelay run --project samples\DnRelay.RunSmokeApp\DnRelay.RunSmokeApp.csproj -- 1000
run builds first under a project-graph-aware build lock when needed, then executes with --no-build. This avoids holding the build lock across the whole process.
In normal use, do not pass --no-build.
dnrelay run already decides whether it needs a coordinated build phase, then invokes dotnet run --no-build internally for the actual execution step.
Pass top-level --no-build only when you intentionally want to skip dnrelay's build phase because you already know the outputs are up to date.
Summary includes:
- status
- target
- duration
- exit code
- log path
- optional raw output streaming
- bounded output tail
Text mode also prints an immediate RUN STARTED block with the target and log path before build coordination or execution begins.
kill
dnrelay stats
dnrelay stats --json
dnrelay kill 12345
dnrelay kill 12345 --json
dnrelay kill *
kill is a repo-local cleanup command for lingering dnrelay-related processes.
Use dnrelay stats first to see active process ids and lock owners for the current repository.
kill accepts:
- a process id shown by
dnrelay stats *to kill every dnrelay-related process for the repository
This is mainly useful for cases such as:
- leftover
testhostprocesses aftertest - long-running
runprocesses you want to stop quickly - stale benchmark processes during dogfooding
stats
dnrelay stats
dnrelay stats --json
stats shows:
- active tracked dnrelay processes for the repository
- active lock owners under
.dnrelay\locks\ - process id, command, target, and start time
- automatic cleanup of stale
.dnrelay\pids\*.jsonand.dnrelay\locks\*.jsonrecords when their owner pid is already gone
This is the primary way to discover ids for dnrelay kill.
bench
dnrelay bench --project perf\Benchmarks\Benchmarks.csproj
dnrelay bench --target perf\Benchmarks\Benchmarks.csproj
dnrelay bench --project perf\Benchmarks\Benchmarks.csproj -- --filter *Parser*
dnrelay bench --project perf\Benchmarks\Benchmarks.csproj --json -- --filter *Parser*
dnrelay bench --project perf\Benchmarks\Benchmarks.csproj -c Release
dnrelay bench --project perf\Benchmarks\Benchmarks.csproj --timeout 10m
dnrelay bench --project perf\Benchmarks\Benchmarks.csproj --logs-dir .artifacts\logs
dnrelay bench --project perf\Benchmarks\Benchmarks.csproj --env DOTNET_TieredPGO=1
dnrelay bench --project perf\Benchmarks\Benchmarks.csproj -- --job short --filter *Parser*
dnrelay bench --project samples\DnRelay.BenchSingleSmokeApp\DnRelay.BenchSingleSmokeApp.csproj
dnrelay bench --project samples\DnRelay.BenchSmokeApp\DnRelay.BenchSmokeApp.csproj
dnrelay bench --project samples\DnRelay.BenchSmokeApp\DnRelay.BenchSmokeApp.csproj --list
dnrelay bench --project samples\DnRelay.BenchSmokeApp\DnRelay.BenchSmokeApp.csproj --select 2
dnrelay bench --project samples\DnRelay.BenchSmokeApp\DnRelay.BenchSmokeApp.csproj --select ParserBenchmarks
dnrelay bench --project samples\DnRelay.BenchSmokeApp\DnRelay.BenchSmokeApp.csproj -- --filter *Parser*
dnrelay bench --project samples\DnRelay.BenchFailureSmokeApp\DnRelay.BenchFailureSmokeApp.csproj
bench is intended for BenchmarkDotNet-style projects. It:
- builds under a project-graph-aware build lock
- releases that lock
- runs the benchmark under a narrower project-scoped bench lock
This keeps build coordination conservative without blocking unrelated repo work for the full benchmark duration.
Summary includes:
- status
- target
- duration
- exit code
- log path
- BenchmarkDotNet artifacts path
- compact highlights
- bounded output tail
- build lock scope summary
- build lock wait duration
Text mode also prints an immediate BENCH STARTED block with the target and log path before build coordination begins.
The full details still live in BenchmarkDotNet.Artifacts.
The sample benchmark projects in this repo cover three dogfooding patterns:
DnRelay.BenchSingleSmokeApp: one benchmark, runs directly without selectionDnRelay.BenchSmokeApp: multiple benchmarks, exercises--list,--select, and--filterDnRelay.BenchFailureSmokeApp: throwing benchmark, exercises benchmark failure reporting
All of them use ShortRunJob so iteration stays fast.
If multiple benchmarks are discovered and you did not pass --select or --filter, the harness prints the benchmark list and exits instead of entering interactive selection.
Use --list to inspect candidates, --select <index> to run one benchmark by number, or --select <name> / --filter for name-based selection.
bench output format
bench has three main output shapes:
- list output
- selection-required output
- progress plus final summary when execution starts
Example for list mode:
BENCH STARTED
target: samples\DnRelay.BenchSmokeApp\DnRelay.BenchSmokeApp.csproj
log: .dnrelay\logs\bench-20260412-121957-221-pid188612.txt
BENCH LIST
project: samples\DnRelay.BenchSmokeApp\DnRelay.BenchSmokeApp.csproj
benchmarks:
- [0] FormattingBenchmarks.JoinWithStringJoin
- [1] FormattingBenchmarks.JoinWithStringBuilder
- [2] ParserBenchmarks.ParseCsv
- [3] ParserBenchmarks.ParsePipeSeparated
Example for selection-required mode:
BENCH STARTED
target: samples\DnRelay.BenchSmokeApp\DnRelay.BenchSmokeApp.csproj
log: .dnrelay\logs\bench-20260412-121957-226-pid193604.txt
BENCH SELECTION REQUIRED
project: samples\DnRelay.BenchSmokeApp\DnRelay.BenchSmokeApp.csproj
benchmarks:
- [0] FormattingBenchmarks.JoinWithStringJoin
- [1] FormattingBenchmarks.JoinWithStringBuilder
- [2] ParserBenchmarks.ParseCsv
- [3] ParserBenchmarks.ParsePipeSeparated
next:
- dnrelay bench --project samples\DnRelay.BenchSmokeApp\DnRelay.BenchSmokeApp.csproj --select 0
- dnrelay bench --project samples\DnRelay.BenchSmokeApp\DnRelay.BenchSmokeApp.csproj --select FormattingBenchmarks.JoinWithStringJoin
- dnrelay bench --project samples\DnRelay.BenchSmokeApp\DnRelay.BenchSmokeApp.csproj -- --filter *SomeBenchmark*
Example for execution mode:
BENCH STARTED
target: samples\DnRelay.BenchSmokeApp\DnRelay.BenchSmokeApp.csproj
log: .dnrelay\logs\bench-20260412-024109-141-pid213648.txt
benchmark running... 15s
BENCH SUCCEEDED
project: samples\DnRelay.BenchSmokeApp\DnRelay.BenchSmokeApp.csproj
duration: 29.1s
exit code: 0
log: .dnrelay\logs\bench-20260412-024109-141-pid213648.txt
artifacts: samples\DnRelay.BenchSmokeApp\BenchmarkDotNet.Artifacts\results
build lock: samples\DnRelay.BenchSmokeApp\DnRelay.BenchSmokeApp.csproj
build lock wait: 0.0s
highlights:
- | Method | Mean | Error | StdDev |
- | ParseCsv | 885.7 ns | 3.42 ns | 3.20 ns |
Field meanings:
benchmark running... <seconds>s- heartbeat only
- indicates the benchmark process is still alive
- not part of the final summary object
<COMMAND> STARTED- immediate text-mode acknowledgement that the harness accepted the command
- emitted before lock wait and before child process execution
- includes the resolved target path and harness log path
- suppressed in
--jsonmode so the final JSON object remains the only stdout payload
BENCH LIST- printed when
--listis used - shows the discovered benchmark methods with stable numeric indices
- printed when
BENCH SELECTION REQUIRED- printed when multiple benchmarks are discovered and no
--selector--filterwas provided - intended to be the default agent-friendly flow for multi-benchmark assemblies
- includes concrete next-command examples instead of entering interactive selection
- printed when multiple benchmarks are discovered and no
BENCH SUCCEEDED/BENCH FAILED/BENCH TIMED OUT- final harness status
reason- optional failure-focused one-line diagnosis
- appears when the harness can identify a benchmark issue such as an exception or invalid benchmark result set
project- resolved benchmark project path relative to the repo root
duration- total harness-observed wall-clock duration, including build phase and benchmark execution
exit code- final process exit code returned by the harness
log- full harness log path
artifacts- BenchmarkDotNet results directory
- this is the primary pointer for detailed benchmark reports such as
.csv,.md, and.html
build lock- the build-phase lock scope used before BenchmarkDotNet execution begins
build lock wait- time spent waiting for the build-phase lock
highlights- selected lines promoted from BenchmarkDotNet output
- intended to give a compact human/agent-readable result without dumping the full log
- successful runs prefer the final summary table rows only
- if the table cannot be extracted, dnrelay falls back to the broader final summary section
- failed runs may instead prefer exception and export lines
output tail- bounded last lines from the benchmark output
- primarily useful for failures and unusual final-context clues
- successful runs may omit it entirely if the tail adds no value
- not intended to be a stable machine-readable contract
JSON Contract
All --json modes emit exactly one final JSON object to stdout.
Shared fields:
commandstatusprojectdurationexitCodetimedOutlog
build --json adds:
warningserrorstopWarningstopErrors
test --json adds:
totalpassedfailedskippederrorstrxtopFailurestopErrors
run --json adds:
outputTail
bench --json adds:
artifactsreasonselectionRequiredavailableBenchmarkshighlightsoutputTail
stats --json adds:
repoprocesseslocksremovedStaleProcessIdsremovedStaleLocks
Each processes[] entry includes:
idcommandtargetstarted
Each locks[] entry includes:
nameownerIdstatecommandtargetstarted
kill --json adds:
selectorrepomatchedPidskilledPidsalreadyGonePidsfailedPidsremovedStaleProcessIdsremovedStaleLocks
Kill-specific status values:
completedno_matchfailed
Bench-specific status values:
listedselection_requiredsucceededfailedtimed_out
What is intentionally not in the main summary:
- full statistical detail such as histogram, quartiles, skewness, kurtosis, and confidence interval breakdown
- full environment dump
- full BenchmarkDotNet transcript
Those remain in:
logartifacts
Practical interpretation:
- use
--listfirst when you do not know the benchmark names - use
--select <index>for the most deterministic follow-up command - read the summary first
- use
highlightsfor quick result inspection - use
output tailfor quick failure/context clues - open
logorBenchmarkDotNet.Artifactswhen you need the full BenchmarkDotNet output contract
Common Options
Supported across the main commands:
--json--timeout <duration>--logs-dir <path>--env KEY=VALUE--env-file <path>
Examples:
dnrelay build src\App\App.csproj --timeout 30s
dnrelay build src\App\App.csproj --logs-dir .artifacts\logs
dnrelay test tests\App.Tests\App.Tests.csproj --env ASPNETCORE_ENVIRONMENT=Development
dnrelay run --project src\App\App.csproj --env-file .env.local
Logging And Artifacts
Harness logs go under:
.dnrelay/logs/
Test result artifacts go under:
artifacts/test-results/
BenchmarkDotNet keeps its own detailed artifacts under the benchmark project, typically:
<benchmark-project-dir>/BenchmarkDotNet.Artifacts/
The harness summary is the primary output. Logs and artifacts are for follow-up investigation.
Logs are written as UTF-8 without BOM, and child process stdout/stderr is decoded as UTF-8 by default so common Windows dotnet output stays readable in the saved transcript.
dnrelay also defaults DOTNET_CLI_UI_LANGUAGE to en-US for its child dotnet processes so CLI and MSBuild output are consistently English unless you override that environment variable explicitly.
You can override the harness log directory per command with --logs-dir <path>.
If the path is relative, it is resolved from the repository root.
You can also generate a repo-local default with:
dnrelay config logs .artifacts\logs
Or write .dnrelay\config.json directly:
{
"logsDir": ".artifacts/logs"
}
Resolution order:
--logs-dir.dnrelay\config.jsonlogsDir- default
.dnrelay\logs\
Locking Model
Current locking policy:
buildandtestresolve a project/solution lock scope from.csproj,.sln, or.slnx- if that scope cannot be resolved safely, they fall back to a repo-wide build lock
runuses the same build lock scope only for the build phase when neededbenchuses the same build lock scope for the build phase, then a project-scoped bench lock for the benchmark run
Lock files and repo-local config live under:
.dnrelay/
In practice that means:
.dnrelay\config.jsonfor repo-local defaults.dnrelay\.gitignoreauto-generated to ignorelogs/,locks/, andpids/.dnrelay\logs\for default harness logs.dnrelay\locks\for build and bench coordination files.dnrelay\pids\for tracked child process metadata used bykill
The intended rule is:
- lock what can be inferred from project/solution/build metadata
- do not try to infer arbitrary runtime resource conflicts such as ports, databases, or external services
If a runtime conflict is outside the project graph, normal failure plus compact summary is acceptable.
Help
Root help:
dnrelay --help
dnrelay-tool --help
Per-command help:
dnrelay build --help
dnrelay test --help
dnrelay run --help
dnrelay bench --help
dnrelay stats --help
dnrelay kill --help
dnrelay-tool tool-refresh --help
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
This package has no dependencies.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 106 | 4/16/2026 |