diff --git a/README.md b/README.md
index 2707324..37ec537 100644
--- a/README.md
+++ b/README.md
@@ -75,13 +75,33 @@ Copy `templates/Directory.Packages.props` from this package to your repo root. A
---
+## Engineering principles
+
+`Purview.BuildSdk` treats naming, placement, and test structure as configuration. Follow the SDK's
+engineering conventions so it can infer namespaces, assembly identities, test categories, project
+references, and default package wiring without bespoke overrides.
+
+Start with:
+
+- [Engineering Principles](docs/wiki/Engineering-Principles.md)
+- [Project Naming Conventions](docs/wiki/Project-Naming-Conventions.md)
+- [Project Type Detection](docs/wiki/Project-Type-Detection.md)
+- [Assembly Name Generation](docs/wiki/Assembly-Name-Generation.md)
+- [Testing Wiring](docs/wiki/Testing-Wiring.md)
+
## Project naming guide
The SDK applies several conventions automatically based on the `.csproj` filename and `NamespacePrefix`.
+The wiki pages above are the source of truth; this section is intentionally brief.
### Defaults (no extra configuration)
-`RootNamespace` is always derived from `$(NamespacePrefix).$(ProjectName)` and is the canonical default public name. By default (`EnableAssemblyNameGeneration=true`), `AssemblyName` and `PackageId` both follow the fully evaluated `RootNamespace`. Test projects retain their detected suffix so test assemblies stay distinct from the source assembly. Set `EnableAssemblyNameGeneration=false` (before the SDK import) to opt out and use standard .NET behaviour (the `.csproj` filename):
+`RootNamespace` is derived from `$(NamespacePrefix).$(ProjectName)` and is the canonical code identity by
+default. `AssemblyName` and `PackageId` normally follow the resolved project identity. Test projects keep
+their detected suffix so test assemblies stay distinct from the source assembly.
+
+Set `EnableAssemblyNameGeneration=false` before the SDK import to opt out and use standard .NET
+project-name behavior.
| `.csproj` filename | `AssemblyName` / `PackageId` | `RootNamespace` | Detected as |
| -- | -- | -- | -- |
@@ -92,64 +112,39 @@ The SDK applies several conventions automatically based on the `.csproj` filenam
> **Note:** `InternalsVisibleTo` follows `$(AssemblyName)` — so for `Api.csproj` the SDK generates `Acme.Api.UnitTests`, `Acme.Api.IntegrationTests`, etc.
-Use short `.csproj` names — the SDK handles the prefixing:
+Use short `.csproj` names - the SDK handles the prefixing:
```text
✅ Api.csproj → short name, SDK resolves the rest
❌ Acme.Api.csproj → redundant prefix, avoid
```
-A build-time check (`PurviewProjectFileNameMismatch`) enforces that the `.csproj` filename matches its parent directory name, preventing inconsistent naming. Set `DisableProjectFileNamingConventionCheck=true` to opt out.
+A build-time check (`PurviewProjectFileNameMismatch`) enforces that the `.csproj` filename matches its
+parent directory name, preventing inconsistent naming. Set `DisableProjectFileNamingConventionCheck=true`
+to opt out.
-### Recommended structure: `src/` + `tests/`
+The detected test type becomes the baseline test category. Additional categories are still allowed when
+they improve discoverability.
-For larger repos, separate source and test projects into `src/` and `tests/` folders:
+The default test stack is also convention-driven. Standard test projects automatically receive:
-```text
-MyRepo/
-├── Directory.Build.props ← NamespacePrefix=Acme
-├── Directory.Build.targets
-├── Directory.Packages.props
-├── global.json
-├── src/
-│ ├── Api/
-│ │ └── Api.csproj
-│ ├── Core/
-│ │ └── Core.csproj
-│ └── SourceGenerator/
-│ └── SourceGenerator.csproj
-├── tests/
-│ ├── Api.UnitTests/
-│ │ └── Api.UnitTests.csproj → IsTestProject=true, TestingType=Unit
-│ ├── Api.IntegrationTests/
-│ │ └── Api.IntegrationTests.csproj
-│ └── SharedTestingFramework/
-│ └── SharedTestingFramework.csproj → IsSharedTestingProject=true
-└── package.json
-```
+- `TUnit`
+- `TUnit.Mocks`
+- `Bogus`
+- Microsoft.Testing.Platform integration
-### Flat structure: everything together
+Specialised additions such as `TUnit.Aspire` and `Testcontainers` stay explicit and intentional.
-For smaller repos, source and test projects can live side-by-side:
+### Recommended structure
-```text
-MyRepo/
-├── Directory.Build.props
-├── Directory.Build.targets
-├── Directory.Packages.props
-├── global.json
-├── Api/
-│ └── Api.csproj
-├── Api.UnitTests/
-│ └── Api.UnitTests.csproj
-├── Core/
-│ └── Core.csproj
-├── Core.IntegrationTests/
-│ └── Core.IntegrationTests.csproj
-└── package.json
-```
+Prefer an explicit `src/` + `tests/` split for new repositories. The canonical SDK-friendly patterns are:
-Both layouts work identically — the SDK detects test projects by name suffix, not folder location.
+- solutions under `src/{SolutionName}.slnx`
+- source projects under `src/src/{ProjectName}/{ProjectName}.csproj`
+- test projects under `src/tests/{ProjectName}.{TestType}Tests/{ProjectName}.{TestType}Tests.csproj`
+
+The SDK also supports flatter layouts, but the explicit source/test split is easier to scale and reason
+about.
### Quick reference
@@ -167,6 +162,10 @@ mkdir Api.UnitTests && cd Api.UnitTests
dotnet new classlib -n Api.UnitTests
```
+For the full layout, naming, identity, and test-readability conventions, see
+[Engineering Principles](docs/wiki/Engineering-Principles.md) and
+[Project Naming Conventions](docs/wiki/Project-Naming-Conventions.md).
+
---
## Template files
diff --git a/docs/wiki/Assembly-Name-Generation.md b/docs/wiki/Assembly-Name-Generation.md
index 4d77064..eebf003 100644
--- a/docs/wiki/Assembly-Name-Generation.md
+++ b/docs/wiki/Assembly-Name-Generation.md
@@ -1,5 +1,8 @@
# Assembly Name Generation
+This page explains the identity-generation rules referenced by
+[Engineering Principles](Engineering-Principles.md).
+
By default (`EnableAssemblyNameGeneration=true`), the SDK treats `RootNamespace` as the canonical
public name: `AssemblyName` and `PackageId` both default to the fully evaluated `RootNamespace` — or,
when suffix-stripping removed a segment of the logical project name, to the full logical project name
@@ -42,4 +45,4 @@ Stripped suffixes: `Core`, `EF`, `Shared`, `ClientShared`, `ServiceDefaults`, an
shared-testing project names.
See [Project Naming Conventions](Project-Naming-Conventions.md) and
-[InternalsVisibleTo](InternalsVisibleTo.md) for related naming behaviour.
\ No newline at end of file
+[InternalsVisibleTo](InternalsVisibleTo.md) for related naming behaviour.
diff --git a/docs/wiki/Configuration-Reference.md b/docs/wiki/Configuration-Reference.md
index 55c9eee..ba07dce 100644
--- a/docs/wiki/Configuration-Reference.md
+++ b/docs/wiki/Configuration-Reference.md
@@ -125,7 +125,8 @@ importing the SDK:
| `TestDataFramework` | `Bogus` | Test data provider. Supported values: `Bogus`, `None`. |
| `DisableAutoInternalsVisibleTo` | `false` | Set to `true` to disable automatic `InternalsVisibleTo` generation for test types and shared testing projects. |
-See [Testing Wiring](Testing-Wiring.md) for details.
+See [Engineering Principles](Engineering-Principles.md) for the policy-level testing guidance and
+[Testing Wiring](Testing-Wiring.md) for the implementation details.
## Compiler-visible SDK properties
@@ -197,4 +198,4 @@ analyzer packages were built with the standard settings.
-```
\ No newline at end of file
+```
diff --git a/docs/wiki/Engineering-Principles.md b/docs/wiki/Engineering-Principles.md
new file mode 100644
index 0000000..73698ba
--- /dev/null
+++ b/docs/wiki/Engineering-Principles.md
@@ -0,0 +1,291 @@
+# Engineering Principles
+
+`Purview.BuildSdk` treats naming, placement, and test structure as configuration. The SDK can only infer
+the right namespaces, identities, references, and test wiring when projects follow a small, predictable set
+of conventions.
+
+This page is the policy-level guide for those conventions. Use it when creating a new repository, adding a
+new project, or rationalising an older repository toward the SDK defaults. The companion pages explain the
+mechanics in detail:
+
+- [Project Naming Conventions](Project-Naming-Conventions.md)
+- [Project Type Detection](Project-Type-Detection.md)
+- [Assembly Name Generation](Assembly-Name-Generation.md)
+- [Testing Wiring](Testing-Wiring.md)
+
+## Core principles
+
+- Naming is part of configuration.
+- Folder placement is part of configuration.
+- `NamespacePrefix` is the root identity source for the repository.
+- `RootNamespace` is the canonical code identity by default.
+- `AssemblyName` and `PackageId` should normally align with the resolved project identity.
+- Test suites should optimise readability at scale, not just local convenience.
+- Automatic SDK behavior should be preferred over manual per-project overrides.
+
+These conventions exist to make repository structure legible to both humans and tools. A repository with
+hundreds or thousands of tests becomes easier to navigate when project names, namespaces, categories, and
+test class names all tell the same story.
+
+## Canonical project layout
+
+The SDK is intentionally tolerant of both `src/` + `tests/` and flat layouts, but new repositories should
+prefer an explicit source/test split.
+
+Recommended layout:
+
+```text
+MyRepo/
+|- Directory.Build.props
+|- Directory.Build.targets
+|- Directory.Packages.props
+|- global.json
+|- package.json
+|- src/
+| |- MyProduct.slnx
+| |- src/
+| | |- Domain/
+| | | '- Domain.csproj
+| | |- Identity.API/
+| | | '- Identity.API.csproj
+| | '- Web.App/
+| | '- Web.App.csproj
+| '- tests/
+| |- Domain.UnitTests/
+| | '- Domain.UnitTests.csproj
+| |- Identity.API.IntegrationTests/
+| | '- Identity.API.IntegrationTests.csproj
+| '- SharedTestingInfra/
+| '- SharedTestingInfra.csproj
+'- README.md
+```
+
+Canonical patterns:
+
+- solution entry point: `src/{SolutionName}.slnx`
+- source projects: `src/src/{ProjectName}/{ProjectName}.csproj`
+- test projects: `src/tests/{ProjectName}.{TestType}Tests/{ProjectName}.{TestType}Tests.csproj`
+
+The `.csproj` filename should match the containing directory name unless the repository explicitly opts out
+with `DisableProjectFileNamingConventionCheck=true`.
+
+## Project naming rules
+
+Use short project names. The SDK applies the prefixing and identity generation for you.
+
+Good examples:
+
+- `Hosting.csproj`
+- `Identity.API.csproj`
+- `Domain.UnitTests.csproj`
+- `SharedTestingInfra.csproj`
+
+Avoid redundant prefixing:
+
+- `Aspire.Hosting.csproj`
+- `Acme.Sales.RegionalPipeline.Identity.API.csproj`
+
+The SDK derives behavior from project names, so near-miss names are a liability. If you want the SDK's
+automatic shared/shared-testing behavior, use its exact recognised names.
+
+Recognised shared project names:
+
+- `Shared`
+- `SharedFramework`
+- `SharedInfrastructure`
+- `SharedInfra`
+- `SharedUtilities`
+- `SharedUtils`
+- `SharedLibrary`
+- `SharedLib`
+- `SharedHelpers`
+
+Recognised shared testing project names:
+
+- `SharedTestingFramework`
+- `SharedTestingInfrastructure`
+- `SharedTestingInfra`
+- `SharedTestingUtilities`
+- `SharedTestingUtils`
+- `SharedTestingLibrary`
+- `SharedTestingLib`
+- `SharedTestingHelpers`
+
+## Namespace, assembly, and package identity
+
+`NamespacePrefix` is the source of truth for repository identity.
+
+The SDK derives a logical project identity from:
+
+- `NamespacePrefix`
+- the project name
+- a small set of suffix-stripping rules
+
+Key rules:
+
+1. `RootNamespace` defaults to the logical project identity.
+2. Duplicate tail segments are collapsed, so an already-prefixed project does not get double-prefixed.
+3. Test suffixes such as `.UnitTests` and `.IntegrationTests` are removed from `RootNamespace`.
+4. Known non-identity suffixes such as `Core`, `EF`, `Shared`, `ClientShared`, and `ServiceDefaults` may be
+ stripped from `RootNamespace`.
+5. `AssemblyName` and `PackageId` normally default to the resolved project identity.
+6. When suffix stripping would otherwise collapse two distinct artifacts into the same identity,
+ `AssemblyName` and `PackageId` keep the fuller logical identity so the artifacts remain distinct.
+
+Examples:
+
+| `NamespacePrefix` | Project name | `RootNamespace` | `AssemblyName` / `PackageId` |
+| -- | -- | -- | -- |
+| `Aspire` | `Hosting` | `Aspire.Hosting` | `Aspire.Hosting` |
+| `Aspire` | `Hosting.UnitTests` | `Aspire.Hosting` | `Aspire.Hosting.UnitTests` |
+| `Acme.Sales.RegionalPipeline` | `Domain` | `Acme.Sales.RegionalPipeline.Domain` | `Acme.Sales.RegionalPipeline.Domain` |
+| `Acme.Sales.RegionalPipeline` | `Identity.API` | `Acme.Sales.RegionalPipeline.Identity.API` | `Acme.Sales.RegionalPipeline.Identity.API` |
+| `Acme.Sales.RegionalPipeline` | `Identity.Core` | `Acme.Sales.RegionalPipeline.Identity` | `Acme.Sales.RegionalPipeline.Identity.Core` |
+| `Acme.Sales.RegionalPipeline` | `Shared` | `Acme.Sales.RegionalPipeline` | `Acme.Sales.RegionalPipeline.Shared` |
+
+This distinction matters when reasoning about packaging, `InternalsVisibleTo`, and test assembly names.
+
+## Automatic project references
+
+The SDK infers project references from naming and placement.
+
+For test projects:
+
+- `TargetProjectName` is inferred from the project name.
+- the target project is auto-discovered from conventional relative paths
+- sibling shared-testing projects are also discovered automatically
+
+For non-test projects:
+
+- sibling shared projects are discovered automatically
+
+This means naming discipline is not cosmetic. It directly affects whether the SDK can connect the right
+projects without manual `ProjectReference` maintenance.
+
+## Test project types and categories
+
+The detected test type becomes the baseline category for the project.
+
+Examples:
+
+- `*.UnitTests` -> `Category=Unit`
+- `*.IntegrationTests` -> `Category=Integration`
+- `*.ContractTests` -> `Category=Contract`
+
+The detected test category is the default category, not the only one. Additional categories are allowed and
+encouraged when they improve filtering and discoverability in large suites.
+
+Recommended common test project types:
+
+- `UnitTests`
+- `IntegrationTests`
+- `E2ETests`
+- `FunctionalTests`
+- `ContractTests`
+
+The SDK also recognises broader suffixes when a repository genuinely needs them, including:
+
+- `AcceptanceTests`
+- `PerformanceTests`
+- `LoadTests`
+- `SmokeTests`
+- `StressTests`
+- `RegressionTests`
+- `SecurityTests`
+- `ScenarioTests`
+- `SystemTests`
+- `ArchitectureTests`
+- `AccessibilityTests`
+- `InteractiveTests`
+- `EnvironmentTests`
+- `WhiteBoxTests`
+- `BlackBoxTests`
+- `ChaosTests`
+- `ThreatTests`
+
+Prefer the smaller common set by default. Reach for the broader set only when the test type itself carries
+important operational meaning.
+
+## Default and specialised test wiring
+
+Standard test projects automatically receive the default test stack from the SDK:
+
+- `TUnit`
+- `TUnit.Mocks`
+- `Bogus`
+- Microsoft.Testing.Platform integration
+
+Shared testing projects receive test-support wiring rather than a runnable test host.
+
+Specialised dependencies stay explicit and intentional:
+
+- `TUnit.Aspire` for Aspire lifecycle or AppHost-backed integration tests
+- `Testcontainers` for container-backed integration tests
+
+Do not manually duplicate the default stack in every test project unless the repository has explicitly opted
+out of the SDK defaults.
+
+## Test readability rules
+
+The goal of the naming conventions is readability when a repository contains thousands of tests.
+
+### Subject-based tests
+
+When a test class owns a specific subject, prefer `{SubjectName}Tests`.
+
+Examples:
+
+- `CustomerIdTests`
+- `ResultsEndpointFilterTests`
+- `AssemblyNameCalculatorTests`
+
+When a test method targets a specific member or subject behavior, use:
+
+`{SubjectOrMemberUnderTest}_{Scenario}_{Expectation}`
+
+Examples:
+
+- `Create_GivenInvalidEmail_ThrowsArgumentException`
+- `Constructor_GivenNullLogger_ThrowsArgumentNullException`
+- `DisplayName_WhenTrimmed_ReturnsNormalizedValue`
+- `CompareTo_GivenHigherVersion_ReturnsPositiveValue`
+
+This format is for subject-based tests: methods, constructors, properties, operators, conversions,
+validation hooks, and similarly well-bounded behavior.
+
+### Non-subject-based suites
+
+Not every useful suite is centered on one subject. Broader suite names are allowed when they are more
+readable and truthful.
+
+Examples:
+
+- `BuildIntegrationTests`
+- `GeneratedPackageAssetsTests`
+- `CrossPlatformSchemaCompatibilityTests`
+- `AppHostLifecycleTests`
+
+Use TUnit features such as display names, categories, and data-driven metadata to keep these broader suites
+discoverable and understandable.
+
+### Async, assertions, and cancellation
+
+- Test methods should be `public async Task`.
+- Assertions should use TUnit `Assert`.
+- When the API under test accepts a `CancellationToken`, the test method should accept
+ `CancellationToken cancellationToken` as its final parameter.
+- Parameters before the token may come from TUnit data sources.
+- The cancellation token should be forwarded to the API under test and to helper methods that also accept
+ one.
+
+## Rationalising existing repositories
+
+Older repositories may not fully match these conventions yet. Apply them pragmatically:
+
+- new projects should follow these rules by default
+- existing projects should converge over time
+- preserve readability and avoid churn-only renames
+- when in doubt, prefer the structure that lets the SDK infer behavior without extra overrides
+
+Use these engineering principles as the policy layer, then use the companion pages for the concrete
+mechanics of detection, naming, identity generation, and test wiring.
diff --git a/docs/wiki/Getting-Started.md b/docs/wiki/Getting-Started.md
index ce3b87d..07c06a1 100644
--- a/docs/wiki/Getting-Started.md
+++ b/docs/wiki/Getting-Started.md
@@ -58,9 +58,10 @@ default to `*` (latest at restore). Pin any package by replacing `*` with a spec
## Next steps
+- Read the policy-level guidance in [Engineering Principles](Engineering-Principles.md).
- Understand how projects are detected and named in [Project Type Detection](Project-Type-Detection.md)
and [Project Naming Conventions](Project-Naming-Conventions.md).
- Browse every configurable property in the [Configuration Reference](Configuration-Reference.md).
- Learn how the package version is sourced in [Version Detection](Version-Detection.md).
- See how test projects are wired up in [Testing Wiring](Testing-Wiring.md).
-- Read how packages are produced in [Packaging](Packaging.md).
\ No newline at end of file
+- Read how packages are produced in [Packaging](Packaging.md).
diff --git a/docs/wiki/Home.md b/docs/wiki/Home.md
index 4809c95..cd85414 100644
--- a/docs/wiki/Home.md
+++ b/docs/wiki/Home.md
@@ -12,6 +12,7 @@ repo root inherits everything automatically.
## Start here
- [Getting Started](Getting-Started.md)
+- [Engineering Principles](Engineering-Principles.md)
- [Project Naming Conventions](Project-Naming-Conventions.md)
- [Project Type Detection](Project-Type-Detection.md)
- [Configuration Reference](Configuration-Reference.md)
@@ -31,6 +32,8 @@ repo root inherits everything automatically.
- **Project type detection** — `IsCSharpProject`, `IsTestProject`, `IsSharedTestingProject`,
`IsContainerProject`, `IsWebSdkProject`, `IsAspireHostProject`, `IsCLIProject`, … driven by the
`.csproj` filename, the `Sdk` attribute, and on-disk markers.
+- **Engineering conventions** — repository layout, project naming, namespace identity, test readability,
+ and the default boundaries between source, test, and shared test-support projects.
- **C# defaults** — `net10.0` TFM (overridable, `netstandard2.0` for Roslyn components),
`LangVersion=preview` (`latest` for Roslyn components), `Nullable=enable`, `ImplicitUsings=enable`,
deterministic builds, `ManagePackageVersionsCentrally=true`.
@@ -71,4 +74,4 @@ repo root inherits everything automatically.
- `src/src/CodeFixers` — Roslyn code-fix assembly (`Purview.BuildSdk.CodeFixers`).
- `src/tests` — unit, integration, and SDK harness test projects.
- `docs/wiki` — this wiki.
-- `templates/` — ready-to-copy starter files for consuming repositories.
\ No newline at end of file
+- `templates/` — ready-to-copy starter files for consuming repositories.
diff --git a/docs/wiki/Project-Naming-Conventions.md b/docs/wiki/Project-Naming-Conventions.md
index b1e494c..9babddd 100644
--- a/docs/wiki/Project-Naming-Conventions.md
+++ b/docs/wiki/Project-Naming-Conventions.md
@@ -1,5 +1,9 @@
# Project Naming Conventions
+This page explains the mechanics behind the higher-level guidance in
+[Engineering Principles](Engineering-Principles.md). Read that page first when you need the policy and
+tradeoffs; use this page when you need the exact naming and layout rules the SDK implements.
+
The SDK applies several conventions automatically based on the `.csproj` filename and
`NamespacePrefix`.
@@ -100,12 +104,14 @@ dotnet new classlib -n Api.UnitTests
## Test project naming conventions
-Test projects are automatically detected by their suffix. Supported patterns:
+Test projects are automatically detected by their suffix. The common recommended set is:
```text
MyProject.UnitTests → IsTestProject=true, TestingType=Unit
MyProject.IntegrationTests→ IsTestProject=true, TestingType=Integration
MyProject.E2ETests → IsTestProject=true, TestingType=E2E
+MyProject.FunctionalTests → IsTestProject=true, TestingType=Functional
+MyProject.ContractTests → IsTestProject=true, TestingType=Contract
```
Any suffix from the full list is recognised: `Unit`, `Integration`, `E2E`, `EndToEnd`, `Acceptance`,
@@ -113,6 +119,9 @@ Any suffix from the full list is recognised: `Unit`, `Integration`, `E2E`, `EndT
`System`, `Threat`, `BlackBox`, `WhiteBox`, `Accessibility`, `Interactive`, `Environment`,
`Architecture`, `Contract`.
+The detected test type becomes the baseline category for the project. Additional categories can still be
+added when they improve discoverability or filtering.
+
## Shared testing projects
Projects named `SharedTestingFramework`, `SharedTestingInfrastructure`, `SharedTestingInfra`,
@@ -121,4 +130,4 @@ Projects named `SharedTestingFramework`, `SharedTestingInfrastructure`, `SharedT
not the test runner or coverage settings.
See [Project Type Detection](Project-Type-Detection.md) for how these names are classified, and
-[Assembly Name Generation](Assembly-Name-Generation.md) for how the identities are derived.
\ No newline at end of file
+[Assembly Name Generation](Assembly-Name-Generation.md) for how the identities are derived.
diff --git a/docs/wiki/Project-Type-Detection.md b/docs/wiki/Project-Type-Detection.md
index 08351de..0938bde 100644
--- a/docs/wiki/Project-Type-Detection.md
+++ b/docs/wiki/Project-Type-Detection.md
@@ -1,5 +1,8 @@
# Project Type Detection
+This page documents the detection mechanics behind the policy described in
+[Engineering Principles](Engineering-Principles.md).
+
During `Sdk.props` evaluation the SDK classifies every `.csproj` by reading the project filename, the
`Sdk` attribute, and on-disk markers. The resulting flags drive the defaults described throughout this
wiki.
@@ -24,7 +27,8 @@ wiki.
`TestingType` is the detected test category suffix from the project name (for example `Unit`,
`Integration`, `E2E`); `TargetProjectName` is the inferred non-test project name that a test project
-targets.
+targets. The detected `TestingType` also becomes the baseline test category, while additional categories
+remain available to the test suite.
## What each type gets
@@ -98,4 +102,4 @@ targets.
The detection flags (plus many SDK properties) are exposed to Roslyn analyzers and source generators
via `CompilerVisibleProperty`, readable as `build_property.`. The full list is in the
-[Configuration Reference](Configuration-Reference.md).
\ No newline at end of file
+[Configuration Reference](Configuration-Reference.md).
diff --git a/docs/wiki/Testing-Wiring.md b/docs/wiki/Testing-Wiring.md
index ede627d..e25bc6b 100644
--- a/docs/wiki/Testing-Wiring.md
+++ b/docs/wiki/Testing-Wiring.md
@@ -1,5 +1,8 @@
# Testing Wiring
+This page explains how the SDK implements the higher-level testing conventions in
+[Engineering Principles](Engineering-Principles.md).
+
The SDK wires the testing stack for you based on three properties that must be set **before** the
SDK import:
@@ -19,8 +22,10 @@ error.
- The `TUnit` package is referenced (with the `Microsoft.Testing.Platform` runner configured via
`global.json`).
- A `[Category: ]` assembly attribute tags every test with its detected test category
- (for example `Unit`, `Integration`), which makes `--treenode-filter` filtering work.
+ (for example `Unit`, `Integration`), which makes `--treenode-filter` filtering work. That category is
+ the default classification, not a limit on adding more categories.
- `TUnit.Mocks` is referenced when `SubstituteFramework=TUnitMocks` (the default).
+- `Bogus` is referenced when `TestDataFramework=Bogus` (the default).
## Xunit (opt-in)
@@ -99,4 +104,4 @@ dotnet test --treenode-filter "/*/*/*/*[Category=Unit]/"
```
See [Project Naming Conventions](Project-Naming-Conventions.md) for the recognised test suffixes and
-[InternalsVisibleTo](InternalsVisibleTo.md) for how test assemblies access internals.
\ No newline at end of file
+[InternalsVisibleTo](InternalsVisibleTo.md) for how test assemblies access internals.
diff --git a/docs/wiki/_Sidebar.md b/docs/wiki/_Sidebar.md
index 8110005..6bf86e7 100644
--- a/docs/wiki/_Sidebar.md
+++ b/docs/wiki/_Sidebar.md
@@ -1,5 +1,6 @@
- [Home](Home.md)
- [Getting Started](Getting-Started.md)
+- [Engineering Principles](Engineering-Principles.md)
- [Project Naming Conventions](Project-Naming-Conventions.md)
- [Project Type Detection](Project-Type-Detection.md)
- [Configuration Reference](Configuration-Reference.md)
@@ -12,4 +13,4 @@
- [Code Fixes](Code-Fixes.md)
- [Repository Bootstrap](Repository-Bootstrap.md)
- [Agent Folder](Agent-Folder.md)
-- [Release Flow](Release-Flow.md)
\ No newline at end of file
+- [Release Flow](Release-Flow.md)
diff --git a/docs/wiki/index.md b/docs/wiki/index.md
index fe6b242..03b9055 100644
--- a/docs/wiki/index.md
+++ b/docs/wiki/index.md
@@ -4,3 +4,4 @@ Centralized MSBuild conventions, analyzers, code fixes, and repository wiring.
[Documentation overview](Home.md){ .md-button .md-button--primary }
[Get started](Getting-Started.md){ .md-button }
+[Engineering principles](Engineering-Principles.md){ .md-button }
diff --git a/src/src/BuildSdk/Sdk/.agents/agents/sdk-consumer-setup.md b/src/src/BuildSdk/Sdk/.agents/agents/sdk-consumer-setup.md
index 8e5066c..c3cc84f 100644
--- a/src/src/BuildSdk/Sdk/.agents/agents/sdk-consumer-setup.md
+++ b/src/src/BuildSdk/Sdk/.agents/agents/sdk-consumer-setup.md
@@ -14,11 +14,14 @@ Help a consuming repository adopt or troubleshoot `Purview.BuildSdk` correctly,
variables, `.git` root, or a nearby `package.json`. `UsePackageJsonVersion=Strict` fails fast instead of
silently skipping resolution.
4. If the bundled `.agents/**` content isn't appearing in the repo root, check `EnableAgentFolderInPackage`
- (default `true`) and `AgentPackDestinationFolder` (default `.agents`) — the copy runs before build via
+ (default `true`) and `AgentPackDestinationFolder` (default `.agents`) - the copy runs before build via
`EnsureAgentFolderInPackageTarget`.
5. For test-framework or project-shape questions, confirm the project follows repo naming and placement
conventions the SDK expects, rather than introducing bespoke structure.
-6. Re-run `dotnet build` (or the repo's canonical build command) after each configuration change to confirm
+6. For naming, layout, and test readability questions, start from the engineering principles: naming and
+ placement are configuration, short project names are preferred, the detected test type becomes the baseline
+ category, and subject-based tests should be named for the subject they own.
+7. Re-run `dotnet build` (or the repo's canonical build command) after each configuration change to confirm
the fix.
## Constraints
@@ -32,3 +35,4 @@ Help a consuming repository adopt or troubleshoot `Purview.BuildSdk` correctly,
## Related skill
See `../skills/sdk-configuration-reference/SKILL.md` for the full property reference.
+See `../skills/sdk-engineering-principles/SKILL.md` for the higher-level repository conventions.
diff --git a/src/src/BuildSdk/Sdk/.agents/agents/sdk-repository-rationaliser.md b/src/src/BuildSdk/Sdk/.agents/agents/sdk-repository-rationaliser.md
new file mode 100644
index 0000000..d41060b
--- /dev/null
+++ b/src/src/BuildSdk/Sdk/.agents/agents/sdk-repository-rationaliser.md
@@ -0,0 +1,38 @@
+# sdk-repository-rationaliser (generic agent spec)
+
+## Goal
+
+Help a repository move toward `Purview.BuildSdk` conventions without unnecessary churn.
+
+## Workflow
+
+1. Start by reading the repo's `Directory.Build.props`, `Directory.Build.targets`, solution entry point,
+ and current project layout.
+2. Identify the current `NamespacePrefix`, project naming scheme, and test project suffixes in use.
+3. Compare the repo's structure against the engineering principles:
+ - short project names
+ - source/test split where practical
+ - exact shared/shared-testing names when SDK behavior is expected
+ - readable, scalable test naming
+4. Separate findings into three groups:
+ - already aligned
+ - misaligned but harmless
+ - misaligned and blocking SDK automatic behavior
+5. Prefer the smallest sequence of changes that improves predictability without forcing broad renames.
+6. When recommending test changes, preserve readable behavior-oriented suites while tightening subject-based
+ suites toward `{SubjectName}Tests` and `{SubjectOrMemberUnderTest}_{Scenario}_{Expectation}`.
+7. Confirm whether specialized dependencies such as `TUnit.Aspire` or `Testcontainers` are genuinely needed
+ rather than treating them as universal defaults.
+
+## Constraints
+
+- Do not assume every older repo should be renamed wholesale.
+- Preserve meaningful established structure unless it interferes with SDK inference.
+- Prefer explaining the effect on `RootNamespace`, `AssemblyName`, `PackageId`, `TestingType`, and
+ `TargetProjectName` instead of arguing from taste.
+
+## Related skills
+
+- `../skills/sdk-engineering-principles/SKILL.md`
+- `../skills/project-placement-defaults/SKILL.md`
+- `../skills/sdk-project-behavior-and-detection/SKILL.md`
diff --git a/src/src/BuildSdk/Sdk/.agents/prompts/sdk-review-repository-shape.md b/src/src/BuildSdk/Sdk/.agents/prompts/sdk-review-repository-shape.md
new file mode 100644
index 0000000..eb089c9
--- /dev/null
+++ b/src/src/BuildSdk/Sdk/.agents/prompts/sdk-review-repository-shape.md
@@ -0,0 +1,33 @@
+# sdk-review-repository-shape (generic prompt spec)
+
+Review a repository that uses `Purview.BuildSdk` for naming, placement, and test-structure alignment.
+
+## Required behaviour
+
+1. Inspect the repository's `Directory.Build.props`, `Directory.Build.targets`, solution entry point, and
+ project layout before making assumptions.
+2. Identify whether the repository follows the SDK-friendly structure:
+ - source projects under `src/`
+ - test projects under `tests/`
+ - `.csproj` filenames matching directory names
+ - short project names with `NamespacePrefix` carrying the repo identity
+3. Check whether test project names use recognised `*Tests` suffixes and whether shared/shared-testing
+ projects use exact SDK-recognised names.
+4. Explain the consequences of deviations in terms of automatic `RootNamespace`, `AssemblyName`,
+ `PackageId`, `TargetProjectName`, and automatic project references.
+5. Review test readability conventions:
+ - subject-based `{SubjectName}Tests`
+ - subject-based `{SubjectOrMemberUnderTest}_{Scenario}_{Expectation}` method names
+ - non-subject-based suites named clearly for their broader role
+ - appropriate use of TUnit categories and display names
+6. Distinguish between:
+ - acceptable existing variance worth preserving
+ - structural debt that blocks the SDK's automatic behavior
+ - incremental rationalisation opportunities
+
+## Suggested output
+
+- A concise summary of whether the repo broadly fits the SDK conventions.
+- A list of concrete mismatches, ordered by impact.
+- A list of low-risk rationalisation steps for naming, placement, identity, or test readability.
+- Explicit note of which behaviors are already automatic defaults and which require manual configuration.
diff --git a/src/src/BuildSdk/Sdk/.agents/skills/project-placement-defaults/SKILL.md b/src/src/BuildSdk/Sdk/.agents/skills/project-placement-defaults/SKILL.md
index b650da8..1c4ff1c 100644
--- a/src/src/BuildSdk/Sdk/.agents/skills/project-placement-defaults/SKILL.md
+++ b/src/src/BuildSdk/Sdk/.agents/skills/project-placement-defaults/SKILL.md
@@ -9,7 +9,9 @@ Use this skill whenever a task asks to add, move, split, or create a project in
## Core principle
-Preserve the host repository's existing layout first; only introduce new structure when no established pattern exists. In Purview-based repos, prefer layouts that let the SDK's naming and auto-reference rules work without extra overrides.
+Preserve the host repository's existing layout first; only introduce new structure when no established pattern exists. In repositories that use `Purview.BuildSdk`, prefer layouts that let the SDK's naming and auto-reference rules work without extra overrides.
+
+Treat naming and placement as configuration, not decoration.
## Placement heuristics
@@ -27,12 +29,13 @@ When a repo has no clear structure, use these conservative defaults because they
- Test projects under `tests/`
- Integration/end-to-end tests in explicit sibling projects/folders such as `tests/Api.IntegrationTests/` or `tests/Api.E2ETests/`
-## Purview-specific naming rules
+## Naming rules
The SDK relies heavily on project names.
- Keep the `.csproj` filename equal to its containing directory name unless `DisableProjectFileNamingConventionCheck=true` is explicitly used.
-- Use conventional test suffixes such as `.UnitTests`, `.IntegrationTests`, `.E2ETests`, `.FunctionalTests`, `.ContractTests`, and other supported `*Tests` suffixes.
+- Use the common conventional test suffixes by default: `.UnitTests`, `.IntegrationTests`, `.E2ETests`, `.FunctionalTests`, `.ContractTests`.
+- Use other supported `*Tests` suffixes only when the test type itself carries important operational meaning.
- Keep shared helper projects on the SDK's exact recognized names when you want shared behavior:
- Shared projects: `Shared`, `SharedFramework`, `SharedInfrastructure`, `SharedInfra`, `SharedUtilities`, `SharedUtils`, `SharedLibrary`, `SharedLib`, `SharedHelpers`
- Shared testing projects: `SharedTestingFramework`, `SharedTestingInfrastructure`, `SharedTestingInfra`, `SharedTestingUtilities`, `SharedTestingUtils`, `SharedTestingLibrary`, `SharedTestingLib`, `SharedTestingHelpers`
@@ -48,6 +51,9 @@ Separate tests by behavior and dependency scope:
If specialized test categories exist (for example, analyzer diagnostics vs code-fix integration), keep category-specific tests in distinct projects/folders.
+The detected test type also becomes the baseline test category. Additional categories remain available and
+should be added when they improve discoverability.
+
The SDK recognizes many test suffixes, including `Unit`, `Integration`, `E2E`, `EndToEnd`, `Acceptance`, `Functional`, `Performance`, `Load`, `Smoke`, `Stress`, `Regression`, `Security`, `Chaos`, `Scenario`, `System`, `Threat`, `BlackBox`, `WhiteBox`, `Accessibility`, `Interactive`, `Environment`, `Architecture`, and `Contract`.
## Naming and namespace defaults
@@ -77,8 +83,20 @@ When working in a Purview-based repo, also assume:
- `TargetFramework` defaults to `net10.0` if not otherwise set, or `netstandard2.0` when the project explicitly declares `IsRoslynComponent=true`.
- Test projects receive framework packages and coverage defaults from the SDK.
+- Standard test projects receive `TUnit`, `TUnit.Mocks`, `Bogus`, and Microsoft.Testing.Platform wiring by default.
- Non-test projects receive SourceLink and telemetry defaults unless explicitly opted out.
+## Test readability defaults
+
+When organizing tests:
+
+- Prefer one subject-focused `{SubjectName}Tests` class per owned subject.
+- Use `{SubjectOrMemberUnderTest}_{Scenario}_{Expectation}` for subject-based test methods.
+- Treat constructors, properties, operators, conversions, and validation hooks as valid subjects.
+- Allow broader suite names for non-subject-based tests such as build, generator, workflow, package, or
+ full-system suites.
+- Use TUnit display names and categories to keep large suites readable.
+
## Move/split workflow checklist
When splitting or relocating tests/projects:
diff --git a/src/src/BuildSdk/Sdk/.agents/skills/sdk-configuration-reference/SKILL.md b/src/src/BuildSdk/Sdk/.agents/skills/sdk-configuration-reference/SKILL.md
index bcc5a55..5dd68c6 100644
--- a/src/src/BuildSdk/Sdk/.agents/skills/sdk-configuration-reference/SKILL.md
+++ b/src/src/BuildSdk/Sdk/.agents/skills/sdk-configuration-reference/SKILL.md
@@ -78,6 +78,16 @@ Primary settings:
- `SubstituteFramework` — default `TUnitMocks`; supported values: `TUnitMocks`, `NSubstitute`, `None`
- `TestDataFramework` — default `Bogus`; supported values: `Bogus`, `None`
+Default outcome for standard test projects:
+
+- `TUnit`
+- `TUnit.Mocks`
+- `Bogus`
+- Microsoft.Testing.Platform integration
+
+Specialised packages such as `TUnit.Aspire` and `Testcontainers` are not automatic defaults; they remain
+explicit choices based on the project's purpose.
+
Related toggles and derived settings:
- `CollectCoverage` — defaults to `true` for detected test projects
@@ -190,3 +200,5 @@ When changing SDK configuration:
2. Keep README, SDK property declarations, validation, and any shipped skills aligned.
3. If you add a new user-facing property, update both the configuration docs and the bundled skills.
4. If the property affects import-time behavior, document that it must be set before the SDK import.
+5. Keep repository policy guidance aligned with the engineering-principles documentation, and keep low-level
+ property explanations aligned with the wiki reference pages.
diff --git a/src/src/BuildSdk/Sdk/.agents/skills/sdk-engineering-principles/SKILL.md b/src/src/BuildSdk/Sdk/.agents/skills/sdk-engineering-principles/SKILL.md
new file mode 100644
index 0000000..c5c3140
--- /dev/null
+++ b/src/src/BuildSdk/Sdk/.agents/skills/sdk-engineering-principles/SKILL.md
@@ -0,0 +1,94 @@
+---
+name: sdk-engineering-principles
+description: "Use when creating or rationalising a repository that uses Purview.BuildSdk and you need the policy-level conventions for project placement, naming, namespace identity, test categories, and large-suite readability."
+---
+
+# Purview.BuildSdk engineering principles
+
+Use this skill when the question is not just "what property does the SDK set?" but "how should this
+repository be structured so the SDK can work predictably?"
+
+## First principle
+
+Treat naming, placement, and test structure as configuration.
+
+The SDK infers namespaces, identities, categories, package wiring, and project references from a small set
+of conventions. The more a repository follows those conventions, the less it needs bespoke overrides.
+
+## Canonical defaults
+
+- Prefer a `src/` + `tests/` split for new repositories.
+- Keep solution entry points under `src/{SolutionName}.slnx`.
+- Keep source projects under `src/src/{ProjectName}/{ProjectName}.csproj`.
+- Keep test projects under `src/tests/{ProjectName}.{TestType}Tests/{ProjectName}.{TestType}Tests.csproj`.
+- Keep the `.csproj` filename equal to its containing directory name.
+
+## Identity rules
+
+- `NamespacePrefix` is the root identity source.
+- Use short project names; let the SDK apply the prefix.
+- `RootNamespace` is the canonical code identity by default.
+- `AssemblyName` and `PackageId` usually follow the resolved project identity.
+- When suffix stripping would collapse distinct artifacts, `AssemblyName` and `PackageId` keep the fuller
+ logical identity.
+
+Examples:
+
+- `NamespacePrefix=Aspire`, project `Hosting` -> `Aspire.Hosting`
+- `NamespacePrefix=Acme.Sales.RegionalPipeline`, project `Identity.API` ->
+ `Acme.Sales.RegionalPipeline.Identity.API`
+- `NamespacePrefix=Acme.Sales.RegionalPipeline`, project `Identity.Core` -> `RootNamespace`
+ `Acme.Sales.RegionalPipeline.Identity`, but a distinct assembly/package identity that keeps `Core`
+
+## Common test project types
+
+Prefer these by default:
+
+- `UnitTests`
+- `IntegrationTests`
+- `E2ETests`
+- `FunctionalTests`
+- `ContractTests`
+
+The SDK supports more suffixes, but use them only when the test type itself is important enough to carry in
+the project name.
+
+## Test categories
+
+- The detected test type becomes the baseline category automatically.
+- Additional categories are allowed.
+- Add more categories when they improve discoverability for large suites.
+
+## Default test stack
+
+Standard test projects receive these by default:
+
+- `TUnit`
+- `TUnit.Mocks`
+- `Bogus`
+- Microsoft.Testing.Platform integration
+
+Specialized additions remain explicit:
+
+- `TUnit.Aspire` for Aspire lifecycle/AppHost-backed integration tests
+- `Testcontainers` for container-backed integration tests
+
+## Test readability rules
+
+For subject-based tests:
+
+- Prefer `{SubjectName}Tests` for the class.
+- Prefer `{SubjectOrMemberUnderTest}_{Scenario}_{Expectation}` for methods.
+- Treat methods, constructors, properties, operators, conversions, and validation hooks as valid subjects.
+
+For non-subject-based suites:
+
+- Broader names are allowed when they are more truthful and readable.
+- Use TUnit display names, categories, and data-driven metadata to keep the suite navigable.
+
+## When to use this skill vs others
+
+- Use this skill for policy, structure, naming, and repository-shape questions.
+- Use `sdk-project-behavior-and-detection` for "why did the SDK classify this project this way?"
+- Use `sdk-configuration-reference` for property-level questions.
+- Use `project-placement-defaults` when physically creating or moving projects.
diff --git a/src/src/BuildSdk/Sdk/.agents/skills/sdk-project-behavior-and-detection/SKILL.md b/src/src/BuildSdk/Sdk/.agents/skills/sdk-project-behavior-and-detection/SKILL.md
index cdd0be3..9961d00 100644
--- a/src/src/BuildSdk/Sdk/.agents/skills/sdk-project-behavior-and-detection/SKILL.md
+++ b/src/src/BuildSdk/Sdk/.agents/skills/sdk-project-behavior-and-detection/SKILL.md
@@ -5,7 +5,7 @@ description: "Use when explaining why Purview.BuildSdk classified a project as t
# Purview.BuildSdk project behavior and detection
-Use this skill when a task asks **why** the SDK applied a behavior automatically, or when adding/moving projects in a repo that relies on Purview’s naming and project-type inference.
+Use this skill when a task asks **why** the SDK applied a behavior automatically, or when adding/moving projects in a repo that relies on the SDK's naming and project-type inference.
## Project-type detection rules
@@ -163,6 +163,16 @@ This is why consistent naming and placement matter so much in repos that use the
- For TUnit test projects, enables Microsoft.Testing.Platform integration properties automatically
- For shared-testing projects, skips the runnable test package and marks them with a skip/category pattern appropriate to the selected test framework
+For the default configuration, standard test projects receive:
+
+- `TUnit`
+- `TUnit.Mocks`
+- `Bogus`
+- Microsoft.Testing.Platform integration
+
+Specialized testing dependencies such as `TUnit.Aspire` and `Testcontainers` are still explicit additions by
+project purpose.
+
### For special project types
- CLI projects default to `OutputType=Exe` and include `appsettings*.json` as content
@@ -200,3 +210,5 @@ When adding or moving projects in a repo using this SDK:
3. Use test project suffixes intentionally so auto-detection and auto-references work.
4. Keep shared helpers in exact shared/shared-testing names if you want the corresponding SDK behavior.
5. If you change a naming rule in the SDK, update the README and the shipped skills together.
+6. If the question is really about repository policy rather than one computed property, point the user to the
+ engineering-principles documentation first, then explain the specific SDK mechanics.