diff --git a/README.md b/README.md
index d3c3345..ec0fee8 100644
--- a/README.md
+++ b/README.md
@@ -12,15 +12,38 @@ Scribe provides **Quill**, a fluent source builder that eliminates raw `StringBu
## Getting Started
-```shell
-dotnet add package BulletsForHumanity.Scribe
+### With the Scribe SDK (Recommended)
+
+**1.** Add a `global.json` to your solution root:
+
+```json
+{
+ "msbuild-sdks": {
+ "BulletsForHumanity.Scribe.Sdk": "0.3.0"
+ }
+}
+```
+
+**2.** Create your analyzer/generator `.csproj`:
+
+```xml
+
+
+
+
+
+
```
-**Requirements:** netstandard2.0 (required by the Roslyn compiler host). See [Project Setup](docs/project-setup.md) for a complete `.csproj` template.
+The SDK handles everything: `netstandard2.0` targeting, `LangVersion`, analyzer packaging, `Stubs.cs` polyfills, and dependency bundling. See [Project Setup](docs/project-setup.md) for details.
-### Creating a Generator Project
+### Manual Setup (Without SDK)
-A minimal generator project that uses Scribe:
+```shell
+dotnet add package BulletsForHumanity.Scribe
+```
+
+A minimal generator project without the SDK:
```xml
@@ -41,6 +64,8 @@ A minimal generator project that uses Scribe:
```
+See [Project Setup](docs/project-setup.md) for a complete `.csproj` template.
+
Consumer projects reference the generator via NuGet (or a local package via [LocalDev](#local-development-localdev)):
```xml
@@ -151,6 +176,7 @@ See [Project Setup & Infrastructure](docs/project-setup.md#local-development-loc
| Component | Purpose |
|-----------|---------|
+| [**Scribe SDK**](docs/project-setup.md#scribe-sdk-recommended) | MSBuild SDK — zero-config project setup for analyzer/generator projects |
| [**Quill**](docs/quill-reference.md) | Fluent source builder — indentation, usings, namespaces, XML docs, type resolution |
| **ScribeHeader** | Assembly-level attribute for branding generated files with a decorative page header |
| **Template** | Minimal `{{key}}` marker substitution for structural shells |
diff --git a/Scribe.Sdk/README.md b/Scribe.Sdk/README.md
new file mode 100644
index 0000000..ddc482a
--- /dev/null
+++ b/Scribe.Sdk/README.md
@@ -0,0 +1,87 @@
+# BulletsForHumanity.Scribe.Sdk
+
+A custom MSBuild SDK that eliminates boilerplate from Roslyn analyzer and source generator projects.
+
+## Quick Start
+
+**1. Add a `global.json`** with the SDK version:
+
+```json
+{
+ "msbuild-sdks": {
+ "BulletsForHumanity.Scribe.Sdk": "0.3.0"
+ }
+}
+```
+
+**2. Use the SDK** in your `.csproj`:
+
+```xml
+
+
+
+
+
+
+```
+
+That's it. The SDK handles everything else:
+
+- `TargetFramework=netstandard2.0` (required by Roslyn compiler host)
+- `LangVersion=14` for modern C# features
+- `EnforceExtendedAnalyzerRules=true` to catch common mistakes
+- Analyzer packaging (`analyzers/dotnet/cs/` placement, embedded PDB)
+- Private dependency bundling (Scribe DLL alongside your analyzer)
+- `Stubs.cs` polyfills for `init`, `record`, `required`, nullable annotations
+- LocalDev multi-repo development infrastructure
+
+## What the SDK Sets
+
+| Property | Default | Purpose |
+|----------|---------|---------|
+| `TargetFramework` | `netstandard2.0` | Required by the Roslyn compiler host |
+| `LangVersion` | `14` | Modern C# features with polyfill stubs |
+| `EnforceExtendedAnalyzerRules` | `true` | Catches analyzer authoring mistakes |
+| `IncludeBuildOutput` | `false` | DLL goes into `analyzers/`, not `lib/` |
+| `PackageType` | `Analyzer` | Marks package as an analyzer |
+| `IncludeSymbols` | `false` | PDB is embedded in the DLL |
+| `DebugType` | `embedded` | IDE debugging support |
+| `Nullable` | `enable` | Null-safety |
+| `GenerateDocumentationFile` | `false` | Not needed for bundled DLLs |
+| `CopyLocalLockFileAssemblies` | `true` | Enables private dependency bundling |
+
+All defaults are set in the early phase (`Sdk.props`) and can be overridden in your `.csproj`. Packaging targets (analyzer DLL placement, dependency bundling) are enforced by the SDK and cannot be overridden.
+
+## Stubs.cs Polyfills
+
+The SDK auto-includes `Stubs.cs` with polyfill types for modern C# features on netstandard2.0:
+
+- `init` setters / `record` types (C# 9)
+- `[ModuleInitializer]`, `[SkipLocalsInit]` (C# 9)
+- `[CallerArgumentExpression]` (C# 10)
+- Interpolated string handlers (C# 10)
+- `required` members (C# 11)
+- `scoped ref` parameters (C# 11)
+- Nullable flow analysis attributes
+
+All polyfills are guarded by `#if !NET5_0_OR_GREATER` and deactivate on modern targets.
+
+**Opt out:** Set `false` if you provide your own.
+
+## Packaging
+
+`dotnet pack` produces a correct analyzer NuGet package:
+
+- Your analyzer DLL is placed in `analyzers/dotnet/cs/`
+- Private dependencies (like Scribe) are bundled alongside
+- Roslyn SDK DLLs are excluded (provided by the compiler host)
+
+## LocalDev
+
+The SDK includes [LocalDev](https://github.com/BulletsForHumanity/Scribe/blob/master/docs/project-setup.md#local-development-localdev) infrastructure for multi-repo development. See the main Scribe documentation for setup instructions.
+
+## Links
+
+- [Scribe Documentation](https://github.com/BulletsForHumanity/Scribe)
+- [Project Setup Guide](https://github.com/BulletsForHumanity/Scribe/blob/master/docs/project-setup.md)
+- [Writing Generators](https://github.com/BulletsForHumanity/Scribe/blob/master/docs/writing-generators.md)
diff --git a/Scribe.Sdk/Scribe.Sdk.csproj b/Scribe.Sdk/Scribe.Sdk.csproj
new file mode 100644
index 0000000..89a7dd6
--- /dev/null
+++ b/Scribe.Sdk/Scribe.Sdk.csproj
@@ -0,0 +1,46 @@
+
+
+
+ BulletsForHumanity.Scribe.Sdk
+ netstandard2.0
+
+ false
+ true
+ false
+
+ $(NoWarn);NU5128;NU5129;NETSDK1212
+
+ true
+ MSBuild SDK for Roslyn analyzer and source generator projects. Provides default project configuration, analyzer packaging targets, netstandard2.0 polyfill stubs, and LocalDev multi-repo development infrastructure. Use with: <Project Sdk="BulletsForHumanity.Scribe.Sdk">
+ roslyn;analyzer;generator;source-generator;msbuild-sdk;scribe
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Scribe.Sdk/Sdk/Sdk.props b/Scribe.Sdk/Sdk/Sdk.props
new file mode 100644
index 0000000..6c231cb
--- /dev/null
+++ b/Scribe.Sdk/Sdk/Sdk.props
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+ netstandard2.0
+
+ 14
+
+ true
+
+ false
+
+ Analyzer
+
+ false
+ embedded
+
+ enable
+
+ false
+
+ $(NoWarn);NU5128
+
+ true
+
+
+
+
+
+ true
+
+
+
+
+
diff --git a/Scribe.Sdk/Sdk/Sdk.targets b/Scribe.Sdk/Sdk/Sdk.targets
new file mode 100644
index 0000000..9e0f681
--- /dev/null
+++ b/Scribe.Sdk/Sdk/Sdk.targets
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_ScribeSdkDeps Include="@(ReferenceCopyLocalPaths)"
+ Condition="'%(ReferenceCopyLocalPaths.NuGetPackageId)' != ''
+ and '$([System.String]::Copy("%(ReferenceCopyLocalPaths.NuGetPackageId)").StartsWith("Microsoft.CodeAnalysis"))' != 'True'
+ and '%(Extension)' == '.dll'" />
+
+
+
+
+
+
+
diff --git a/Scribe.Sdk/content/Stubs.cs b/Scribe.Sdk/content/Stubs.cs
new file mode 100644
index 0000000..7f80565
--- /dev/null
+++ b/Scribe.Sdk/content/Stubs.cs
@@ -0,0 +1,266 @@
+// Polyfill stubs for modern C# language features on netstandard2.0.
+//
+// These types are recognised by the compiler by their well-known fully-qualified names.
+// Marking them internal prevents conflicts with the real BCL types when the binary is
+// loaded in a .NET 5+ host (which already carries the real definitions).
+//
+// Feature map:
+// C# 9 init setters / record types → IsExternalInit
+// C# 9 [ModuleInitializer] → ModuleInitializerAttribute
+// C# 9 [SkipLocalsInit] → SkipLocalsInitAttribute
+// C# 10 CallerArgumentExpression → CallerArgumentExpressionAttribute
+// C# 10 Interpolated string handlers → InterpolatedStringHandlerAttribute
+// InterpolatedStringHandlerArgumentAttribute
+// C# 11 required members → RequiredMemberAttribute
+// CompilerFeatureRequiredAttribute
+// C# 11 scoped ref parameters → ScopedRefAttribute
+// Nullable flow analysis attributes → System.Diagnostics.
+
+// ── System.Runtime.CompilerServices ──────────────────────────────────────────
+
+#if !NET5_0_OR_GREATER
+#pragma warning disable IDE0130 // Namespace does not match folder structure
+namespace System.Runtime.CompilerServices
+#pragma warning restore IDE0130 // Namespace does not match folder structure
+{
+ ///
+ /// Marks the init accessor and is required for record types.
+ /// The compiler looks for this class by its fully-qualified name.
+ ///
+ internal static class IsExternalInit { }
+
+ /// Enables the required modifier on members (C# 11).
+ [AttributeUsage(
+ AttributeTargets.Class
+ | AttributeTargets.Struct
+ | AttributeTargets.Field
+ | AttributeTargets.Property,
+ Inherited = false,
+ AllowMultiple = false
+ )]
+ internal sealed class RequiredMemberAttribute : Attribute { }
+
+ ///
+ /// Companion to . The compiler emits this on
+ /// every constructor of a type that has required members (C# 11).
+ ///
+ [AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
+ internal sealed class CompilerFeatureRequiredAttribute : Attribute
+ {
+ public CompilerFeatureRequiredAttribute(string featureName) => FeatureName = featureName;
+
+ public string FeatureName { get; }
+
+ ///
+ /// When , a compiler that does not understand this feature
+ /// is permitted to ignore it. When (the default), the
+ /// compiler must reject the construct.
+ ///
+ public bool IsOptional { get; init; }
+
+ public const string RefStructs = nameof(RefStructs);
+ public const string RequiredMembers = nameof(RequiredMembers);
+ }
+
+ /// Enables [ModuleInitializer] on a static void method (C# 9).
+ [AttributeUsage(AttributeTargets.Method, Inherited = false)]
+ internal sealed class ModuleInitializerAttribute : Attribute { }
+
+ ///
+ /// Suppresses zero-initialisation of locals in the decorated method or type (C# 9).
+ ///
+ [AttributeUsage(
+ AttributeTargets.Module
+ | AttributeTargets.Class
+ | AttributeTargets.Struct
+ | AttributeTargets.Interface
+ | AttributeTargets.Constructor
+ | AttributeTargets.Method
+ | AttributeTargets.Property
+ | AttributeTargets.Event,
+ Inherited = false
+ )]
+ internal sealed class SkipLocalsInitAttribute : Attribute { }
+
+ ///
+ /// Captures the source-text of the argument passed to a decorated parameter (C# 10).
+ ///
+ [AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
+ internal sealed class CallerArgumentExpressionAttribute : Attribute
+ {
+ public CallerArgumentExpressionAttribute(string parameterName) =>
+ ParameterName = parameterName;
+
+ public string ParameterName { get; }
+ }
+
+ /// Marks a type as a custom interpolated string handler (C# 10).
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false)]
+ internal sealed class InterpolatedStringHandlerAttribute : Attribute { }
+
+ ///
+ /// Specifies which arguments of a method call are passed to a custom interpolated
+ /// string handler (C# 10).
+ ///
+ [AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
+ internal sealed class InterpolatedStringHandlerArgumentAttribute : Attribute
+ {
+ public InterpolatedStringHandlerArgumentAttribute(string argument) =>
+ Arguments = [argument];
+
+ public InterpolatedStringHandlerArgumentAttribute(params string[] arguments) =>
+ Arguments = arguments;
+
+ public string[] Arguments { get; }
+ }
+
+ ///
+ /// Indicates that a parameter is scoped — its ref-safety scope does not
+ /// extend beyond the method boundary (C# 11).
+ ///
+ [AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
+ internal sealed class ScopedRefAttribute : Attribute { }
+}
+
+// ── System.Diagnostics.CodeAnalysis — nullable flow analysis ─────────────────
+
+namespace System.Diagnostics.CodeAnalysis
+{
+ ///
+ /// Specifies that an output will not be even if the
+ /// corresponding type allows it.
+ ///
+ [AttributeUsage(
+ AttributeTargets.Field
+ | AttributeTargets.Parameter
+ | AttributeTargets.Property
+ | AttributeTargets.ReturnValue,
+ Inherited = false
+ )]
+ internal sealed class NotNullAttribute : Attribute { }
+
+ ///
+ /// Specifies that an output may be even if the
+ /// corresponding type does not allow it.
+ ///
+ [AttributeUsage(
+ AttributeTargets.Field
+ | AttributeTargets.Parameter
+ | AttributeTargets.Property
+ | AttributeTargets.ReturnValue,
+ Inherited = false
+ )]
+ internal sealed class MaybeNullAttribute : Attribute { }
+
+ /// Specifies that is allowed as an input even if the type does not allow it.
+ [AttributeUsage(
+ AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property,
+ Inherited = false
+ )]
+ internal sealed class AllowNullAttribute : Attribute { }
+
+ /// Specifies that is disallowed as an input even if the type allows it.
+ [AttributeUsage(
+ AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property,
+ Inherited = false
+ )]
+ internal sealed class DisallowNullAttribute : Attribute { }
+
+ ///
+ /// Specifies that when the method returns , the
+ /// decorated parameter will not be .
+ ///
+ [AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
+ internal sealed class NotNullWhenAttribute : Attribute
+ {
+ public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
+
+ public bool ReturnValue { get; }
+ }
+
+ ///
+ /// Specifies that when the method returns , the
+ /// decorated parameter or return value may be .
+ ///
+ [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.ReturnValue, Inherited = false)]
+ internal sealed class MaybeNullWhenAttribute : Attribute
+ {
+ public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
+
+ public bool ReturnValue { get; }
+ }
+
+ ///
+ /// Specifies that the return value of the decorated member is non-null when the
+ /// named parameter is non-null.
+ ///
+ [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.ReturnValue, Inherited = false)]
+ internal sealed class NotNullIfNotNullAttribute : Attribute
+ {
+ public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName;
+
+ public string ParameterName { get; }
+ }
+
+ /// Applied to a method that will never return under any circumstance.
+ [AttributeUsage(AttributeTargets.Method, Inherited = false)]
+ internal sealed class DoesNotReturnAttribute : Attribute { }
+
+ ///
+ /// Specifies that the method will not return if the decorated boolean parameter
+ /// has the specified value.
+ ///
+ [AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
+ internal sealed class DoesNotReturnIfAttribute : Attribute
+ {
+ public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue;
+
+ public bool ParameterValue { get; }
+ }
+
+ ///
+ /// Specifies that the method or property ensures that the listed members are not
+ /// .
+ ///
+ [AttributeUsage(
+ AttributeTargets.Method | AttributeTargets.Property,
+ Inherited = false,
+ AllowMultiple = true
+ )]
+ internal sealed class MemberNotNullAttribute : Attribute
+ {
+ public MemberNotNullAttribute(string member) => Members = [member];
+
+ public MemberNotNullAttribute(params string[] members) => Members = members;
+
+ public string[] Members { get; }
+ }
+
+ ///
+ /// Specifies that the method or property ensures that the listed members are not
+ /// when returning the specified value.
+ ///
+ [AttributeUsage(
+ AttributeTargets.Method | AttributeTargets.Property,
+ Inherited = false,
+ AllowMultiple = true
+ )]
+ internal sealed class MemberNotNullWhenAttribute : Attribute
+ {
+ public MemberNotNullWhenAttribute(bool returnValue, string member)
+ {
+ ReturnValue = returnValue;
+ Members = [member];
+ }
+
+ public MemberNotNullWhenAttribute(bool returnValue, params string[] members)
+ {
+ ReturnValue = returnValue;
+ Members = members;
+ }
+
+ public bool ReturnValue { get; }
+ public string[] Members { get; }
+ }
+}
+#endif
diff --git a/Scribe.slnx b/Scribe.slnx
index 8c7d2b0..ce85d1b 100644
--- a/Scribe.slnx
+++ b/Scribe.slnx
@@ -1,4 +1,5 @@
+
diff --git a/docs/architecture-infrastructure.md b/docs/architecture-infrastructure.md
index f6e1e32..29f75ab 100644
--- a/docs/architecture-infrastructure.md
+++ b/docs/architecture-infrastructure.md
@@ -1,14 +1,68 @@
# How the Infrastructure Works
-Internal architecture of Scribe's build infrastructure — the LocalDev system. Read this if you're contributing to Scribe or want to understand the MSBuild mechanics.
+Internal architecture of Scribe's build infrastructure — the Scribe SDK and LocalDev system. Read this if you're contributing to Scribe or want to understand the MSBuild mechanics.
For setup instructions, see [Project Setup & Infrastructure](project-setup.md).
---
-## File Layout
+## Scribe SDK
-The infrastructure lives in `Scribe/build/` and is shipped inside the NuGet package:
+The Scribe SDK (`BulletsForHumanity.Scribe.Sdk`) is a custom MSBuild SDK that wraps `Microsoft.NET.Sdk` and auto-configures all boilerplate for Roslyn analyzer/generator projects.
+
+### Package Layout
+
+```
+BulletsForHumanity.Scribe.Sdk.nupkg
+ Sdk/
+ Sdk.props <- Chains to Microsoft.NET.Sdk, sets analyzer defaults
+ Sdk.targets <- Auto-includes Stubs.cs, defines packaging targets
+ build/
+ Scribe.LocalDev.props <- Shared LocalDev infrastructure (early phase)
+ Scribe.LocalDev.targets <- Shared LocalDev infrastructure (late phase)
+ content/
+ Stubs.cs <- netstandard2.0 polyfills, injected as Compile item
+```
+
+### SDK Resolution
+
+MSBuild resolves custom SDKs from NuGet packages that contain `Sdk/Sdk.props` and/or `Sdk/Sdk.targets`. Consumers declare the SDK version in `global.json`:
+
+```json
+{
+ "msbuild-sdks": {
+ "BulletsForHumanity.Scribe.Sdk": "0.3.0"
+ }
+}
+```
+
+### Sdk.props (Early Phase)
+
+Runs before the project file is evaluated. Sets overridable defaults:
+
+1. **Chains to `Microsoft.NET.Sdk`** via ``
+2. **Sets analyzer project defaults**: `TargetFramework=netstandard2.0`, `LangVersion=14`, `EnforceExtendedAnalyzerRules=true`, `IncludeBuildOutput=false`, `PackageType=Analyzer`, embedded PDB, nullable enabled
+3. **Initialises `ScribeSdkIncludeStubs`** to `true` (opt-out via `false`)
+4. **Imports `Scribe.LocalDev.props`** for sentinel file detection and local NuGet source registration
+
+All properties can be overridden by the consuming `.csproj` because they're set in the early phase.
+
+### Sdk.targets (Late Phase)
+
+Runs after the project file. Enforces packaging behaviour:
+
+1. **Chains to `Microsoft.NET.Sdk`** targets
+2. **Auto-includes `Stubs.cs`** as a `Compile` item with `Link="Generated\Stubs.cs"` (invisible in Solution Explorer). Guarded by `ScribeSdkIncludeStubs=true`.
+3. **`_ScribeSdkAddAnalyzerDlls` target**: Places the analyzer DLL into `analyzers/dotnet/cs/` in the NuGet package
+4. **`_ScribeSdkAddAnalyzerDependencies` target**: Bundles private NuGet dependencies alongside the analyzer DLL. Excludes `Microsoft.CodeAnalysis.*` DLLs (provided by the compiler host).
+5. **Sets `CopyLocalLockFileAssemblies=true`** to enable dependency bundling
+6. **Imports `Scribe.LocalDev.targets`** for version override wildcard import
+
+---
+
+## LocalDev File Layout
+
+The LocalDev infrastructure lives in `Scribe/build/` and is shipped inside both the `BulletsForHumanity.Scribe` NuGet package and the `BulletsForHumanity.Scribe.Sdk` package:
```
Scribe/build/
diff --git a/docs/project-setup.md b/docs/project-setup.md
index 2674d54..1287950 100644
--- a/docs/project-setup.md
+++ b/docs/project-setup.md
@@ -4,7 +4,71 @@ This guide covers how to configure a source generator or analyzer project that u
---
-## Generator Project Configuration
+## Scribe SDK (Recommended)
+
+The simplest way to set up a generator or analyzer project is with the **Scribe SDK**. It handles all boilerplate automatically.
+
+### 1. Add a `global.json`
+
+```json
+{
+ "msbuild-sdks": {
+ "BulletsForHumanity.Scribe.Sdk": "0.3.0"
+ }
+}
+```
+
+### 2. Use the SDK in your `.csproj`
+
+```xml
+
+
+
+
+
+
+```
+
+That's it. The SDK sets all required properties, includes `Stubs.cs` polyfills, and configures analyzer packaging automatically.
+
+### What the SDK provides
+
+| Property | Default | Purpose |
+|----------|---------|---------|
+| `TargetFramework` | `netstandard2.0` | Required by the Roslyn compiler host |
+| `LangVersion` | `14` | Modern C# features with polyfill stubs |
+| `EnforceExtendedAnalyzerRules` | `true` | Catches analyzer authoring mistakes |
+| `IncludeBuildOutput` | `false` | DLL goes into `analyzers/`, not `lib/` |
+| `PackageType` | `Analyzer` | Marks package as an analyzer |
+| `IncludeSymbols` | `false` | PDB is embedded in the DLL |
+| `DebugType` | `embedded` | IDE debugging support |
+| `Nullable` | `enable` | Null-safety |
+| `CopyLocalLockFileAssemblies` | `true` | Enables private dependency bundling |
+
+All defaults are set in the early phase (`Sdk.props`) and can be overridden in your `.csproj`. Packaging targets (analyzer DLL placement, dependency bundling) are enforced by the SDK and cannot be overridden.
+
+### Stubs.cs opt-out
+
+The SDK auto-includes `Stubs.cs` polyfills. If you provide your own, opt out:
+
+```xml
+
+ false
+
+```
+
+### Packaging
+
+`dotnet pack` produces a correct analyzer NuGet package automatically:
+- Your analyzer DLL is placed in `analyzers/dotnet/cs/`
+- Private dependencies (like Scribe) are bundled alongside
+- Roslyn SDK DLLs are excluded (provided by the compiler host)
+
+---
+
+## Manual Configuration (Without SDK)
+
+If you prefer not to use the SDK, configure your project manually.
Roslyn analyzers and source generators must target **netstandard2.0** — this is a hard requirement from the compiler host.