From b51dad04ebe88de5e7317c1980fc10ef045acf66 Mon Sep 17 00:00:00 2001 From: Kieron Lanning Date: Thu, 1 Oct 2026 14:04:39 +0100 Subject: [PATCH] docs: added/ updateed all of the docs --- README.md | 91 +++--- docs/wiki/Assembly-Name-Generation.md | 5 +- docs/wiki/Configuration-Reference.md | 5 +- docs/wiki/Engineering-Principles.md | 291 ++++++++++++++++++ docs/wiki/Getting-Started.md | 3 +- docs/wiki/Home.md | 5 +- docs/wiki/Project-Naming-Conventions.md | 13 +- docs/wiki/Project-Type-Detection.md | 8 +- docs/wiki/Testing-Wiring.md | 9 +- docs/wiki/_Sidebar.md | 3 +- docs/wiki/index.md | 1 + .../Sdk/.agents/agents/sdk-consumer-setup.md | 8 +- .../agents/sdk-repository-rationaliser.md | 38 +++ .../prompts/sdk-review-repository-shape.md | 33 ++ .../project-placement-defaults/SKILL.md | 24 +- .../sdk-configuration-reference/SKILL.md | 12 + .../sdk-engineering-principles/SKILL.md | 94 ++++++ .../SKILL.md | 14 +- 18 files changed, 593 insertions(+), 64 deletions(-) create mode 100644 docs/wiki/Engineering-Principles.md create mode 100644 src/src/BuildSdk/Sdk/.agents/agents/sdk-repository-rationaliser.md create mode 100644 src/src/BuildSdk/Sdk/.agents/prompts/sdk-review-repository-shape.md create mode 100644 src/src/BuildSdk/Sdk/.agents/skills/sdk-engineering-principles/SKILL.md 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.