
Archunitnet
- 22 installs
- 466 repo stars
- Updated July 25, 2026
- managedcode/dotnet-skills
Helps with ai & agent building tasks.
About
archunitnet is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- archunitnet
- AI & Agent Building
- AI-coding skill
Archunitnet by the numbers
- 22 all-time installs (skills.sh)
- +4 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #10,137 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/managedcode/dotnet-skills --skill archunitnetAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 22 |
|---|---|
| repo stars | ★ 466 |
| Last updated | July 25, 2026 |
| Repository | managedcode/dotnet-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
ArchUnitNET for .NET
Trigger On
- the repo uses or wants
ArchUnitNET - architecture testing needs richer modeling than simple dependency checks
Value
- produce a concrete project delta: code, docs, config, tests, CI, or review artifact
- reduce ambiguity through explicit planning, verification, and final validation skills
- leave reusable project context so future tasks are faster and safer
Do Not Use For
- the lightest possible architecture rule checks
Inputs
- the nearest
AGENTS.md - target assemblies
- architecture boundaries and naming conventions
Quick Start
1. Read the nearest AGENTS.md and confirm scope and constraints. 2. Run this skill's Workflow through the Ralph Loop until outcomes are acceptable. 3. Return the Required Result Format with concrete artifacts and verification evidence.
Workflow
1. Load the architecture once per test assembly where possible. 2. Encode a small number of durable, high-value architecture rules first. 3. Use the test-framework-specific integration package that matches the repo.
Bootstrap When Missing
If ArchUnitNET is not configured yet:
1. Detect existing setup:
rg -n "TngTech\\.ArchUnitNET" -g '*.csproj' .
2. Add packages to the architecture test project:
dotnet add TEST_PROJECT.csproj package TngTech.ArchUnitNET- add one framework bridge package:
TngTech.ArchUnitNET.xUnit,TngTech.ArchUnitNET.xUnitV3,TngTech.ArchUnitNET.MSTestV2, orTngTech.ArchUnitNET.TUnit
3. Add at least one durable boundary rule test. 4. Wire architecture tests into the standard test command in AGENTS.md and CI. 5. Run dotnet test TEST_PROJECT.csproj and return status: configured or status: improved. 6. If NetArchTest already covers the same boundary policy and no gap exists, return status: not_applicable.
Deliver
- architecture tests with richer domain and type modeling
- architecture-rule commands wired into repo test flow and CI expectations
Validate
- architecture load cost is reasonable for the suite
- rules are stable and tied to real boundaries
Ralph Loop
Use the Ralph Loop for every task, including docs, architecture, testing, and tooling work.
1. Plan first (mandatory):
- analyze current state
- define target outcome, constraints, and risks
- write a detailed execution plan
- list final validation skills to run at the end, with order and reason
2. Execute one planned step and produce a concrete delta. 3. Review the result and capture findings with actionable next fixes. 4. Apply fixes in small batches and rerun the relevant checks or review steps. 5. Update the plan after each iteration. 6. Repeat until outcomes are acceptable or only explicit exceptions remain. 7. If a dependency is missing, bootstrap it or return status: not_applicable with explicit reason and fallback path.
Required Result Format
status:complete|clean|improved|configured|not_applicable|blockedplan: concise plan and current iteration stepactions_taken: concrete changes madevalidation_skills: final skills run, or skipped with reasonsverification: commands, checks, or review evidence summaryremaining: top unresolved items ornone
For setup-only requests with no execution, return status: configured and exact next commands.
Load References
- references/archunitnet.md
- references/patterns.md
- references/examples.md
Example Requests
- "Use ArchUnitNET for layered architecture tests."
- "Set up ArchUnitNET with xUnit or MSTest."
{
"version": "1.0.0",
"category": "Architecture",
"packages": [
"ArchUnitNET",
"ArchUnitNET.xUnit",
"ArchUnitNET.NUnit",
"ArchUnitNET.MSTest"
]
}
ArchUnitNET
Open/Free Status
- open source
- free to use
Install
Core package:
dotnet add package TngTech.ArchUnitNETFramework integration packages vary by test framework, for example:
dotnet add package TngTech.ArchUnitNET.xUnit
dotnet add package TngTech.ArchUnitNET.xUnitV3
dotnet add package TngTech.ArchUnitNET.MSTestV2
dotnet add package TngTech.ArchUnitNET.TUnitVerify First
Before adding packages, check whether the repo already references ArchUnitNET and which framework integration it uses:
rg -n "TngTech\\.ArchUnitNET" -g '*.csproj' .Common Usage
Load the target assemblies once, then assert rules in tests.
Good fit for:
- layered architecture
- namespace rules
- dependency restrictions
- domain boundary checks
CI Fit
- runs as part of the normal test suite
- richer than lightweight architecture-rule libraries, but also heavier
When Not To Use
- when simple
NetArchTestrules already cover the needed constraints
Sources
ArchUnitNET Common Architecture Rules
Project Setup
Base Test Class
using ArchUnitNET.Domain;
using ArchUnitNET.Loader;
using ArchUnitNET.Fluent;
using static ArchUnitNET.Fluent.ArchRuleDefinition;
public abstract class ArchitectureTestBase
{
protected static readonly Architecture Architecture =
new ArchLoader()
.LoadAssemblies(
typeof(Domain.Marker).Assembly,
typeof(Application.Marker).Assembly,
typeof(Infrastructure.Marker).Assembly,
typeof(Web.Marker).Assembly)
.Build();
}Clean Architecture Rules
Domain Layer Independence
The domain layer should have no dependencies on outer layers:
[Fact]
public void Domain_Should_Not_Reference_Application()
{
Types()
.That().ResideInNamespace("MyApp.Domain")
.Should().NotDependOnAny(
Types().That().ResideInNamespace("MyApp.Application"))
.Check(Architecture);
}
[Fact]
public void Domain_Should_Not_Reference_Infrastructure()
{
Types()
.That().ResideInNamespace("MyApp.Domain")
.Should().NotDependOnAny(
Types().That().ResideInNamespace("MyApp.Infrastructure"))
.Check(Architecture);
}
[Fact]
public void Domain_Should_Not_Reference_Presentation()
{
Types()
.That().ResideInNamespace("MyApp.Domain")
.Should().NotDependOnAny(
Types().That().ResideInNamespace("MyApp.Web"))
.Check(Architecture);
}Application Layer Rules
[Fact]
public void Application_Should_Not_Reference_Infrastructure()
{
Types()
.That().ResideInNamespace("MyApp.Application")
.Should().NotDependOnAny(
Types().That().ResideInNamespace("MyApp.Infrastructure"))
.Check(Architecture);
}
[Fact]
public void Application_Should_Not_Reference_Presentation()
{
Types()
.That().ResideInNamespace("MyApp.Application")
.Should().NotDependOnAny(
Types().That().ResideInNamespace("MyApp.Web"))
.Check(Architecture);
}Infrastructure Layer Rules
[Fact]
public void Infrastructure_Should_Not_Reference_Presentation()
{
Types()
.That().ResideInNamespace("MyApp.Infrastructure")
.Should().NotDependOnAny(
Types().That().ResideInNamespace("MyApp.Web"))
.Check(Architecture);
}Naming Convention Rules
Controllers
[Fact]
public void Controllers_Should_Have_Controller_Suffix()
{
Types()
.That().ResideInNamespace("Controllers", includeSubNamespaces: true)
.And().AreClasses()
.And().AreNotAbstract()
.Should().HaveNameEndingWith("Controller")
.Check(Architecture);
}
[Fact]
public void Controllers_Should_Inherit_ControllerBase()
{
Types()
.That().HaveNameEndingWith("Controller")
.And().ResideInNamespace("Controllers")
.Should().BeAssignableTo(typeof(ControllerBase))
.Check(Architecture);
}Services
[Fact]
public void Services_Should_Have_Service_Suffix()
{
Types()
.That().ResideInNamespace("Services")
.And().AreClasses()
.And().AreNotAbstract()
.Should().HaveNameEndingWith("Service")
.Check(Architecture);
}
[Fact]
public void Service_Interfaces_Should_Start_With_I()
{
Types()
.That().ResideInNamespace("Services")
.And().AreInterfaces()
.Should().HaveNameStartingWith("I")
.Check(Architecture);
}Repositories
[Fact]
public void Repositories_Should_Have_Repository_Suffix()
{
Types()
.That().ImplementInterface(typeof(IRepository<>))
.And().AreClasses()
.Should().HaveNameEndingWith("Repository")
.Check(Architecture);
}Handlers (CQRS/MediatR)
[Fact]
public void Handlers_Should_Have_Handler_Suffix()
{
Types()
.That().ImplementInterface(typeof(IRequestHandler<,>))
.Should().HaveNameEndingWith("Handler")
.Check(Architecture);
}
[Fact]
public void Commands_Should_Have_Command_Suffix()
{
Types()
.That().ImplementInterface(typeof(IRequest<>))
.And().ResideInNamespaceContaining("Commands")
.Should().HaveNameEndingWith("Command")
.Check(Architecture);
}
[Fact]
public void Queries_Should_Have_Query_Suffix()
{
Types()
.That().ImplementInterface(typeof(IRequest<>))
.And().ResideInNamespaceContaining("Queries")
.Should().HaveNameEndingWith("Query")
.Check(Architecture);
}Domain-Driven Design Rules
Entities
[Fact]
public void Entities_Should_Reside_In_Domain()
{
Types()
.That().AreAssignableTo(typeof(Entity))
.Should().ResideInNamespaceContaining("Domain")
.Check(Architecture);
}
[Fact]
public void Entities_Should_Not_Have_Public_Setters()
{
// Use custom predicate or property-level checks
Types()
.That().AreAssignableTo(typeof(Entity))
.Should().BeSealed()
.OrShould().BeAbstract()
.Check(Architecture);
}Value Objects
[Fact]
public void ValueObjects_Should_Be_Sealed()
{
Types()
.That().AreAssignableTo(typeof(ValueObject))
.And().AreNotAbstract()
.Should().BeSealed()
.Check(Architecture);
}Aggregate Roots
[Fact]
public void Only_AggregateRoots_Should_Be_Referenced_By_Repositories()
{
Types()
.That().HaveNameEndingWith("Repository")
.Should().DependOnAny(
Types().That().ImplementInterface(typeof(IAggregateRoot)))
.Check(Architecture);
}API Design Rules
DTOs
[Fact]
public void DTOs_Should_Not_Reference_Domain_Entities()
{
Types()
.That().HaveNameEndingWith("Dto")
.Or().HaveNameEndingWith("Request")
.Or().HaveNameEndingWith("Response")
.Should().NotDependOnAny(
Types().That().ResideInNamespace("Domain.Entities"))
.Check(Architecture);
}No Circular Dependencies
[Fact]
public void Application_Should_Be_Free_Of_Cycles()
{
Types()
.That().ResideInNamespace("MyApp.Application")
.Should().BeFreeOfCycles()
.Check(Architecture);
}Infrastructure Rules
Database Contexts
[Fact]
public void DbContexts_Should_Reside_In_Infrastructure()
{
Types()
.That().AreAssignableTo(typeof(DbContext))
.Should().ResideInNamespaceContaining("Infrastructure")
.Check(Architecture);
}External Service Clients
[Fact]
public void HttpClients_Should_Reside_In_Infrastructure()
{
Types()
.That().HaveNameEndingWith("Client")
.And().DependOnAny(Types().That().HaveName("HttpClient"))
.Should().ResideInNamespaceContaining("Infrastructure")
.Check(Architecture);
}Dependency Injection Rules
Interface Implementations
[Fact]
public void Every_Service_Should_Have_Interface()
{
Types()
.That().HaveNameEndingWith("Service")
.And().AreClasses()
.And().AreNotAbstract()
.Should().ImplementInterface(
Types().That().AreInterfaces()
.And().HaveNameStartingWith("I"))
.Check(Architecture);
}Testing Rules
Test Classes Naming
[Fact]
public void Test_Classes_Should_Have_Tests_Suffix()
{
Types()
.That().ResideInAssembly(typeof(SomeTest).Assembly)
.And().AreClasses()
.And().HaveAnyMethodWithAttribute(typeof(FactAttribute))
.Should().HaveNameEndingWith("Tests")
.Check(TestArchitecture);
}Complete Test Class Example
using ArchUnitNET.Domain;
using ArchUnitNET.Loader;
using ArchUnitNET.Fluent;
using ArchUnitNET.xUnit;
using Xunit;
using static ArchUnitNET.Fluent.ArchRuleDefinition;
namespace MyApp.Tests.Architecture;
public class CleanArchitectureTests
{
private static readonly Architecture Architecture =
new ArchLoader()
.LoadAssemblies(
typeof(Domain.DomainAssemblyMarker).Assembly,
typeof(Application.ApplicationAssemblyMarker).Assembly,
typeof(Infrastructure.InfrastructureAssemblyMarker).Assembly,
typeof(Web.WebAssemblyMarker).Assembly)
.Build();
// Layer definitions
private static readonly IObjectProvider<IType> DomainLayer =
Types().That().ResideInNamespace("MyApp.Domain").As("Domain Layer");
private static readonly IObjectProvider<IType> ApplicationLayer =
Types().That().ResideInNamespace("MyApp.Application").As("Application Layer");
private static readonly IObjectProvider<IType> InfrastructureLayer =
Types().That().ResideInNamespace("MyApp.Infrastructure").As("Infrastructure Layer");
private static readonly IObjectProvider<IType> PresentationLayer =
Types().That().ResideInNamespace("MyApp.Web").As("Presentation Layer");
[Fact]
public void Domain_Should_Have_No_Dependencies_On_Other_Layers()
{
Types()
.That().Are(DomainLayer)
.Should().NotDependOnAny(ApplicationLayer)
.AndShould().NotDependOnAny(InfrastructureLayer)
.AndShould().NotDependOnAny(PresentationLayer)
.Check(Architecture);
}
[Fact]
public void Application_Should_Only_Depend_On_Domain()
{
Types()
.That().Are(ApplicationLayer)
.Should().NotDependOnAny(InfrastructureLayer)
.AndShould().NotDependOnAny(PresentationLayer)
.Check(Architecture);
}
[Fact]
public void Controllers_Should_Follow_Naming_Convention()
{
Types()
.That().Are(PresentationLayer)
.And().AreAssignableTo(typeof(Microsoft.AspNetCore.Mvc.ControllerBase))
.Should().HaveNameEndingWith("Controller")
.Check(Architecture);
}
[Fact]
public void Services_Should_Be_Internal()
{
Types()
.That().HaveNameEndingWith("Service")
.And().AreClasses()
.And().DoNotHaveNameStartingWith("I")
.Should().BeInternal()
.Check(Architecture);
}
}ArchUnitNET Rule Patterns
Architecture Loading
Load the architecture once per test class to avoid repeated assembly scanning:
private static readonly Architecture Architecture =
new ArchLoader()
.LoadAssemblies(typeof(SomeClassInTargetAssembly).Assembly)
.Build();For multiple assemblies:
private static readonly Architecture Architecture =
new ArchLoader()
.LoadAssemblies(
typeof(Domain.Entity).Assembly,
typeof(Application.Service).Assembly,
typeof(Infrastructure.Repository).Assembly)
.Build();Rule Syntax Patterns
Basic Pattern
IArchRule rule = Types()
.That()
.Are(predicate)
.Should()
.Be(condition);
rule.Check(Architecture);With Test Framework Integration
Using xUnit:
[Fact]
public void Controllers_Should_Not_Depend_On_Repositories()
{
Types()
.That().ResideInNamespace("Controllers")
.Should().NotDependOnAny(Types().That().ResideInNamespace("Repositories"))
.Check(Architecture);
}Using MSTest:
[TestMethod]
public void Controllers_Should_Not_Depend_On_Repositories()
{
Types()
.That().ResideInNamespace("Controllers")
.Should().NotDependOnAny(Types().That().ResideInNamespace("Repositories"))
.Check(Architecture);
}Selection Patterns
By Namespace
Types().That().ResideInNamespace("MyApp.Domain")
Types().That().ResideInNamespaceContaining("Services")
Types().That().ResideInNamespaceMatching(@"MyApp\..*\.Handlers")By Naming Convention
Types().That().HaveNameEndingWith("Controller")
Types().That().HaveNameStartingWith("I")
Types().That().HaveNameMatching(@".*Handler$")By Type Characteristics
Types().That().AreClasses()
Types().That().AreInterfaces()
Types().That().AreAbstract()
Types().That().AreSealed()
Types().That().ArePublic()
Types().That().ImplementInterface(typeof(IService))By Inheritance
Types().That().AreAssignableTo(typeof(BaseClass))
Types().That().ImplementInterface(typeof(IHandler<>))By Attributes
Types().That().HaveAnyAttributes(typeof(SerializableAttribute))
Types().That().DoNotHaveAnyAttributes(typeof(ObsoleteAttribute))Condition Patterns
Dependency Rules
.Should().NotDependOnAny(Types().That().ResideInNamespace("Infrastructure"))
.Should().OnlyDependOn(Types().That().ResideInNamespace("Domain"))
.Should().DependOnAny(Types().That().ResideInNamespace("Contracts"))Namespace Residence
.Should().ResideInNamespace("MyApp.Application")
.Should().ResideInNamespaceContaining("Handlers")Naming Requirements
.Should().HaveNameEndingWith("Service")
.Should().HaveNameStartingWith("I")Visibility Requirements
.Should().BePublic()
.Should().BeInternal()
.Should().BeSealed()Inheritance Requirements
.Should().ImplementInterface(typeof(IDisposable))
.Should().BeAssignableTo(typeof(BaseEntity))Layer Definition Pattern
Define layers as reusable predicates:
private static readonly IObjectProvider<IType> DomainLayer =
Types().That().ResideInNamespace("MyApp.Domain").As("Domain Layer");
private static readonly IObjectProvider<IType> ApplicationLayer =
Types().That().ResideInNamespace("MyApp.Application").As("Application Layer");
private static readonly IObjectProvider<IType> InfrastructureLayer =
Types().That().ResideInNamespace("MyApp.Infrastructure").As("Infrastructure Layer");
private static readonly IObjectProvider<IType> PresentationLayer =
Types().That().ResideInNamespace("MyApp.Web").As("Presentation Layer");Use layers in rules:
[Fact]
public void Domain_Should_Not_Depend_On_Application()
{
Types().That().Are(DomainLayer)
.Should().NotDependOnAny(ApplicationLayer)
.Check(Architecture);
}Combining Rules
Combine multiple conditions:
Types()
.That().ResideInNamespace("MyApp.Domain")
.And().AreClasses()
.And().AreNotAbstract()
.Should().BeSealed()
.OrShould().ImplementInterface(typeof(IEntity))
.Check(Architecture);Custom Predicates
Create reusable custom predicates:
public static class CustomPredicates
{
public static IPredicate<IType> AreAggregateRoots()
{
return new SimplePredicate<IType>(
type => type.ImplementsInterface(typeof(IAggregateRoot)),
"are aggregate roots");
}
}Use in rules:
Types().That().Are(CustomPredicates.AreAggregateRoots())
.Should().ResideInNamespace("MyApp.Domain.Aggregates")
.Check(Architecture);Slice Pattern for Vertical Slices
Check that vertical slices do not depend on each other:
SliceRuleDefinition.ForFunctions()
.Should().NotDependOnEachOther()
.Check(Architecture);Cycle Detection Pattern
Check for circular dependencies:
Types().That().ResideInNamespace("MyApp")
.Should().BeFreeOfCycles()
.Check(Architecture);