diff --git a/README.md b/README.md
index ec0fee8..6574791 100644
--- a/README.md
+++ b/README.md
@@ -177,6 +177,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 |
+| [**Solution-Local Analyzers**](docs/solution-local-analyzers.md) | Auto-pack intra-solution analyzers — no NuGet publishing needed |
| [**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 |
@@ -194,6 +195,7 @@ See [Project Setup & Infrastructure](docs/project-setup.md#local-development-loc
|-------|---------------|
| [Writing Generators with Quill](docs/writing-generators.md) | How to structure a generator using the Transform → Register → Render pattern |
| [Project Setup & Infrastructure](docs/project-setup.md) | .csproj configuration, packaging, LocalDev multi-repo workflow |
+| [Solution-Local Analyzers](docs/solution-local-analyzers.md) | Setup guide for intra-solution analyzers and generators |
| [Quill Feature Reference](docs/quill-reference.md) | Complete API reference for every Quill method and scope type |
| [How Quill Works](docs/architecture-quill.md) | Internal architecture — type resolution, indentation, XML doc insertion |
| [How the Infrastructure Works](docs/architecture-infrastructure.md) | Build system internals — LocalDev props/targets, override file generation |
diff --git a/Scribe.Sdk/README.md b/Scribe.Sdk/README.md
index ddc482a..507a9a7 100644
--- a/Scribe.Sdk/README.md
+++ b/Scribe.Sdk/README.md
@@ -80,6 +80,24 @@ All polyfills are guarded by `#if !NET5_0_OR_GREATER` and deactivate on modern t
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.
+## Solution-Local Analyzers
+
+For analyzers that live inside the same solution they serve, set `ScribeSolutionAnalyzer=true` to auto-pack on build with automatic version management:
+
+```xml
+
+
+ true
+
+
+
+
+
+
+```
+
+Each build produces a unique timestamp version and a version override file, using the same mechanism as [LocalDev](https://github.com/BulletsForHumanity/Scribe/blob/master/docs/project-setup.md#local-development-localdev) but scoped to the solution. See [Solution-Local Analyzers](https://github.com/BulletsForHumanity/Scribe/blob/master/docs/solution-local-analyzers.md) for the complete setup guide.
+
## Links
- [Scribe Documentation](https://github.com/BulletsForHumanity/Scribe)
diff --git a/Scribe.Sdk/Scribe.Sdk.csproj b/Scribe.Sdk/Scribe.Sdk.csproj
index 89a7dd6..741a423 100644
--- a/Scribe.Sdk/Scribe.Sdk.csproj
+++ b/Scribe.Sdk/Scribe.Sdk.csproj
@@ -3,7 +3,8 @@
BulletsForHumanity.Scribe.Sdk — MSBuild SDK for Roslyn analyzer/generator projects.
This is a pack-only project. It produces no build output — the NuGet package
- contains only MSBuild props/targets, polyfill stubs, and LocalDev infrastructure.
+ contains only MSBuild props/targets, polyfill stubs, Solution-Local Analyzer
+ infrastructure, and LocalDev infrastructure.
-->
BulletsForHumanity.Scribe.Sdk
@@ -18,7 +19,7 @@
$(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">
+ MSBuild SDK for Roslyn analyzer and source generator projects. Provides default project configuration, analyzer packaging targets, netstandard2.0 polyfill stubs, solution-local analyzer auto-pack support, and LocalDev multi-repo development infrastructure. Use with: <Project Sdk="BulletsForHumanity.Scribe.Sdk">
roslyn;analyzer;generator;source-generator;msbuild-sdk;scribe
@@ -38,6 +39,12 @@
+
+
+
+
+
+
diff --git a/Scribe.Sdk/Sdk/Sdk.props b/Scribe.Sdk/Sdk/Sdk.props
index 6c231cb..8471c46 100644
--- a/Scribe.Sdk/Sdk/Sdk.props
+++ b/Scribe.Sdk/Sdk/Sdk.props
@@ -56,6 +56,9 @@
true
+
+
+
diff --git a/Scribe.Sdk/Sdk/Sdk.targets b/Scribe.Sdk/Sdk/Sdk.targets
index 9e0f681..3832a11 100644
--- a/Scribe.Sdk/Sdk/Sdk.targets
+++ b/Scribe.Sdk/Sdk/Sdk.targets
@@ -53,6 +53,9 @@
+
+
+
diff --git a/Scribe/build/Scribe.SolutionAnalyzer.props b/Scribe/build/Scribe.SolutionAnalyzer.props
new file mode 100644
index 0000000..5f7b01e
--- /dev/null
+++ b/Scribe/build/Scribe.SolutionAnalyzer.props
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+ <_ScribeSaTimestamp>$([System.DateTime]::UtcNow.ToString('yyyyMMddHHmmss'))
+ 0.0.0-dev.$(_ScribeSaTimestamp)
+ 0.0.0-dev.$(_ScribeSaTimestamp)
+
+
+ true
+ true
+
+
+ false
+
+
+
+
+
+ <_ScribeSaArtifactsDir>$([MSBuild]::EnsureTrailingSlash('$(ArtifactsPath)'))
+
+
+
+
+
+ $(_ScribeSaArtifactsDir)packages\
+
+
+
+
+
+
+ $(RestoreAdditionalProjectSources);$(_ScribeSaArtifactsDir)packages
+
+
+
diff --git a/Scribe/build/Scribe.SolutionAnalyzer.targets b/Scribe/build/Scribe.SolutionAnalyzer.targets
new file mode 100644
index 0000000..8e83133
--- /dev/null
+++ b/Scribe/build/Scribe.SolutionAnalyzer.targets
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+ <_ScribeSaVersion>$(NuGetPackageVersion)
+ <_ScribeSaVersion Condition="'$(_ScribeSaVersion)' == ''">$(PackageVersion)
+ <_ScribeSaArtifactsDir>$([MSBuild]::EnsureTrailingSlash('$(ArtifactsPath)'))
+ <_ScribeSaOverridePath>$(_ScribeSaArtifactsDir)$(PackageId).Directory.Packages.targets
+
+
+
+ <_ScribeSaLine Include="<Project>" />
+ <_ScribeSaLine Include=" <!-- Auto-generated by Scribe SolutionAnalyzer — do not edit. Rebuild the analyzer to refresh. -->" />
+ <_ScribeSaLine Include=" <ItemGroup>" />
+ <_ScribeSaLine Include=" <PackageVersion Update="$(PackageId)" Version="$(_ScribeSaVersion)" />" />
+ <_ScribeSaLine Include=" </ItemGroup>" />
+ <_ScribeSaLine Include="</Project>" />
+
+
+
+
+
+
+
diff --git a/docs/architecture-infrastructure.md b/docs/architecture-infrastructure.md
index 29f75ab..098f690 100644
--- a/docs/architecture-infrastructure.md
+++ b/docs/architecture-infrastructure.md
@@ -1,6 +1,6 @@
# How the Infrastructure Works
-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.
+Internal architecture of Scribe's build infrastructure — the Scribe SDK, Solution-Local Analyzer, and LocalDev systems. 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).
@@ -20,6 +20,8 @@ BulletsForHumanity.Scribe.Sdk.nupkg
build/
Scribe.LocalDev.props <- Shared LocalDev infrastructure (early phase)
Scribe.LocalDev.targets <- Shared LocalDev infrastructure (late phase)
+ Scribe.SolutionAnalyzer.props <- Solution-local analyzer support (early phase)
+ Scribe.SolutionAnalyzer.targets <- Solution-local analyzer support (late phase)
content/
Stubs.cs <- netstandard2.0 polyfills, injected as Compile item
```
@@ -43,7 +45,8 @@ 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
+4. **Imports `Scribe.SolutionAnalyzer.props`** for solution-local analyzer auto-pack configuration
+5. **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.
@@ -56,7 +59,67 @@ Runs after the project file. Enforces packaging behaviour:
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
+6. **Imports `Scribe.SolutionAnalyzer.targets`** for solution-local override file generation
+7. **Imports `Scribe.LocalDev.targets`** for version override wildcard import
+
+---
+
+## Solution-Local Analyzer
+
+The Solution-Local Analyzer infrastructure enables intra-solution analyzer development — analyzers that live inside the same solution as the projects they serve, without publishing to NuGet. It uses the **same mechanism** as LocalDev (version override files + NuGet source registration) but scoped to a single solution.
+
+For setup instructions, see [Solution-Local Analyzers](solution-local-analyzers.md).
+
+### File Layout
+
+```
+Scribe/build/
+ Scribe.SolutionAnalyzer.props <- Configuration (early phase)
+ Scribe.SolutionAnalyzer.targets <- Override file generation (late phase)
+```
+
+### Props Phase (Early)
+
+`Scribe.SolutionAnalyzer.props` runs when `ScribeSolutionAnalyzer=true`:
+
+1. **Generates a timestamp version** `0.0.0-dev.yyyyMMddHHmmss` — forces Visual Studio to reload analyzers on every build
+2. **Disables NBGV** (`NerdbankGitVersioningEnabled=false`) — version is managed by the SDK
+3. **Enables auto-pack** via `GeneratePackageOnBuild=true`
+4. **Normalises `$(ArtifactsPath)`** to a guaranteed trailing separator (`_ScribeSaArtifactsDir`) for safe path concatenation
+5. **Redirects pack output** to `$(ArtifactsPath)packages/`
+6. **Registers as NuGet source** via `RestoreAdditionalProjectSources` (for the analyzer project itself)
+
+### Targets Phase (Late)
+
+`Scribe.SolutionAnalyzer.targets` defines one target:
+
+**`_ScribeSolutionAnalyzerOverride`** — Runs after Pack. Reads `$(NuGetPackageVersion)` (falling back to `$(PackageVersion)`) and generates a `.Directory.Packages.targets` file in `$(ArtifactsPath)` containing a `PackageVersion Update` entry:
+
+```xml
+
+
+
+
+
+
+```
+
+Consuming projects import this file via wildcard from `$(ArtifactsPath)` in their `Directory.Build.targets`. This is the same pattern as LocalDev's `_ScribeLocalDevOverride` target.
+
+### Relationship to LocalDev
+
+Solution-Local Analyzer and LocalDev use the same underlying mechanism but at different scopes:
+
+| Concern | LocalDev | Solution-Local Analyzer |
+|---------|----------|------------------------|
+| Scope | Cross-repo | Intra-solution |
+| Trigger | `.localscribe` sentinel | `ScribeSolutionAnalyzer=true` |
+| Version | NBGV + timestamp suffix | Timestamp-only (`0.0.0-dev.yyyyMMddHHmmss`) |
+| Override files | Generated `.Directory.Packages.targets` | Same — generated `.Directory.Packages.targets` |
+| Package directory | Shared `$(ScribeRoot)/.artifacts/packages/` | Solution `$(ArtifactsPath)packages/` |
+| Override file location | `$(ScribeRoot)/.artifacts/` | `$(ArtifactsPath)` |
+
+Both features are independent and can coexist. A solution can have solution-local analyzers and also participate in a LocalDev chain.
---
diff --git a/docs/project-setup.md b/docs/project-setup.md
index 1287950..0b23244 100644
--- a/docs/project-setup.md
+++ b/docs/project-setup.md
@@ -143,6 +143,34 @@ For test projects that instantiate generators directly via `CSharpGeneratorDrive
---
+## Solution-Local Analyzers
+
+If your analyzer or generator lives inside the same solution it serves (and is not published to NuGet), the Scribe SDK can auto-pack it on build and make it available to other projects in the solution via standard `PackageReference`.
+
+Set `ScribeSolutionAnalyzer=true` in your analyzer `.csproj`:
+
+```xml
+
+
+ true
+
+
+
+
+
+
+```
+
+Consuming projects reference it like any NuGet package (the version is managed automatically via an override file):
+
+```xml
+
+```
+
+See [Solution-Local Analyzers](solution-local-analyzers.md) for the complete setup guide, including consumer-side `Directory.Build.props` and `.targets` configuration.
+
+---
+
## Packaging
### Placing analyzer DLLs
diff --git a/docs/solution-local-analyzers.md b/docs/solution-local-analyzers.md
new file mode 100644
index 0000000..84986da
--- /dev/null
+++ b/docs/solution-local-analyzers.md
@@ -0,0 +1,260 @@
+# Solution-Local Analyzers
+
+How to set up Roslyn analyzers and source generators that live inside the same solution they serve — no NuGet publishing, no cross-repo setup.
+
+---
+
+## Overview
+
+A **solution-local analyzer** is a Roslyn analyzer or source generator project that exists inside the same solution as the projects it serves. It is not published to NuGet. It is not consumed cross-repo. It automates *this* codebase and nothing else.
+
+Examples:
+
+- A generator that reads domain model types and emits mapping code, DTOs, or validation rules
+- An analyzer that enforces solution-specific coding conventions
+- A generator that emits strongly-typed configuration accessors from appsettings schema
+
+The Scribe SDK provides first-class support for this workflow. When `ScribeSolutionAnalyzer=true` is set on a Scribe SDK project, the SDK automatically:
+
+1. **Packs the analyzer on every build** — produces a `.nupkg` in the solution's `artifacts/packages/` directory
+2. **Generates a unique timestamp version** (`0.0.0-dev.yyyyMMddHHmmss`) — forces Visual Studio to reload the analyzer on every build
+3. **Generates a version override file** — consuming projects automatically resolve the locally-built package via the same mechanism as [LocalDev](project-setup.md#local-development-localdev)
+4. **Bundles private dependencies** — the same dependency bundling as any Scribe SDK project
+
+Consuming projects reference the analyzer via standard ``, getting all the same analyzer wiring that any published package gets.
+
+---
+
+## Quick Start
+
+### 1. Create the analyzer project
+
+```xml
+
+
+ true
+
+
+
+
+
+
+```
+
+### 2. Configure the solution for local packages
+
+The solution must use artifact output. In `Directory.Build.props`:
+
+```xml
+
+ true
+ $(MSBuildThisFileDirectory).artifacts/
+
+
+
+
+
+ $(RestoreAdditionalProjectSources);$(ArtifactsPath)packages
+
+
+```
+
+In `Directory.Build.targets`, import override files generated by solution-local analyzers:
+
+```xml
+
+```
+
+### 3. Reference from consuming projects
+
+Add the analyzer to your `Directory.Packages.props` (any version — the override file pins the actual version):
+
+```xml
+
+
+```
+
+Then reference it from consuming projects:
+
+```xml
+
+
+
+```
+
+### 4. Build
+
+```shell
+dotnet build MySolution.sln
+```
+
+The analyzer auto-packs on build. The version override file is generated automatically. Consuming projects resolve the package from the artifacts directory. Diagnostics and generated code appear automatically.
+
+---
+
+## How It Works
+
+### Auto-Pack on Build
+
+When `ScribeSolutionAnalyzer=true`, the SDK sets `GeneratePackageOnBuild=true` and redirects `PackageOutputPath` to `$(ArtifactsPath)packages/`. Every build produces a fresh `.nupkg`.
+
+### Timestamp Version
+
+Each build produces a unique version: `0.0.0-dev.yyyyMMddHHmmss` (e.g. `0.0.0-dev.20260406054112`). This serves two purposes:
+
+1. **Forces Visual Studio to reload** — VS detects a new analyzer version and reloads the analyzer assembly, picking up changes immediately
+2. **Eliminates NuGet cache issues** — each version is unique, so NuGet never serves stale cached content
+
+### Version Override File
+
+After each pack, the SDK generates a `.Directory.Packages.targets` file in the artifacts directory:
+
+```xml
+
+
+
+
+
+
+```
+
+This is the same mechanism as [LocalDev](project-setup.md#local-development-localdev). Consuming projects import the file via wildcard from `$(ArtifactsPath)`, which updates the `PackageVersion` to match the freshly-built package.
+
+### Package Directory
+
+Packages are placed in `$(ArtifactsPath)packages/` — the standard artifacts packages directory. This requires the solution to use artifact output (`UseArtifactsOutput=true`), which is the recommended setup for modern .NET projects.
+
+---
+
+## Solution Layout
+
+A typical solution with a solution-local analyzer:
+
+```
+MySolution/
+ MySolution.sln
+ Directory.Build.props ← UseArtifactsOutput, RestoreAdditionalProjectSources
+ Directory.Build.targets ← Import override files from .artifacts/
+ Directory.Packages.props ← PackageVersion Include for MyAnalyzer
+ .gitignore ← includes .artifacts/
+ .artifacts/ ← auto-generated, git-ignored
+ packages/
+ MyAnalyzer.0.0.0-dev.20260406054112.nupkg
+ MyAnalyzer.Directory.Packages.targets ← version override file
+ MyAnalyzer/
+ MyAnalyzer.csproj ← ScribeSolutionAnalyzer=true
+ MyGenerator.cs
+ MyApp/
+ MyApp.csproj ←
+ Program.cs
+```
+
+---
+
+## Multiple Analyzers
+
+A solution can have multiple solution-local analyzers. Each generates its own override file in the artifacts directory:
+
+```xml
+
+
+
+ true
+
+ ...
+
+```
+
+```xml
+
+
+
+ true
+
+ ...
+
+```
+
+The wildcard import in `Directory.Build.targets` picks up all override files:
+
+```
+.artifacts/
+ FirstAnalyzer.Directory.Packages.targets
+ SecondAnalyzer.Directory.Packages.targets
+ packages/
+ FirstAnalyzer.0.0.0-dev.20260406054112.nupkg
+ SecondAnalyzer.0.0.0-dev.20260406054112.nupkg
+```
+
+---
+
+## Relationship to LocalDev
+
+Solution-local analyzers use the **same mechanism** as LocalDev (version override files + NuGet source registration) but scoped to a single solution:
+
+| Concern | LocalDev | Solution-Local Analyzer |
+|---------|----------|------------------------|
+| Scope | Cross-repo (e.g. Scribe → Hermetic → MyApp) | Intra-solution |
+| Trigger | `.localscribe` sentinel file | `ScribeSolutionAnalyzer=true` property |
+| Published? | Yes (eventually to NuGet) | Never |
+| Version management | NBGV + timestamp + override files | Timestamp-only + override files |
+| Package directory | Shared `$(ScribeRoot)/.artifacts/packages/` | Solution `$(ArtifactsPath)packages/` |
+| Override file location | `$(ScribeRoot)/.artifacts/` | `$(ArtifactsPath)` |
+| Setup complexity | `$(ScribeRoot)`, trigger project, package names | One property + consumer-side imports |
+
+Both features are independent and can coexist. A solution can have solution-local analyzers and also participate in a LocalDev chain.
+
+---
+
+## Provided Properties
+
+| Property | Value | Set by |
+|----------|-------|--------|
+| `$(ScribeSolutionAnalyzer)` | `true` | Developer (in `.csproj`) |
+| `$(Version)` | `0.0.0-dev.yyyyMMddHHmmss` | SDK (timestamp-based) |
+| `$(PackageVersion)` | `0.0.0-dev.yyyyMMddHHmmss` | SDK (same as Version) |
+| `$(GeneratePackageOnBuild)` | `true` | SDK |
+| `$(PackageOutputPath)` | `$(ArtifactsPath)packages\` | SDK |
+
+---
+
+## Design Decisions
+
+| Decision | Choice | Rationale |
+|----------|--------|-----------|
+| Detection mechanism | Explicit `ScribeSolutionAnalyzer=true` property | Simple, discoverable, no magic. Convention-based detection would be ambiguous. |
+| Version strategy | Timestamp-based `0.0.0-dev.yyyyMMddHHmmss` | Unique version per build forces Visual Studio to reload analyzers. Eliminates NuGet cache issues. |
+| Package directory | `$(ArtifactsPath)packages/` | Uses the solution's existing artifact output directory. No separate `.packages/` directory needed. |
+| Override files | Same mechanism as LocalDev | Proven pattern — `PackageVersion Update` entries in auto-generated `.Directory.Packages.targets` files, imported via wildcard. |
+| Consumer-side setup | Manual one-time setup (`Directory.Build.props` + `.targets`) | Same pattern as LocalDev. All projects in the solution share the same `Directory.Build.*` files, so one-time setup covers everything. |
+
+---
+
+## Troubleshooting
+
+### Consuming project doesn't see the analyzer
+
+1. Verify `$(ArtifactsPath)packages/` contains a `.nupkg` for the analyzer
+2. Verify `Directory.Build.props` registers `$(ArtifactsPath)packages` in `RestoreAdditionalProjectSources`
+3. Verify `Directory.Build.targets` imports `$(ArtifactsPath)*.Directory.Packages.targets`
+4. Run `dotnet restore` on the consuming project
+5. In Visual Studio, try **Build → Rebuild Solution**
+
+### Stale generated code after analyzer changes
+
+Each build produces a unique timestamp version, which forces Visual Studio to reload the analyzer. If you still see stale output:
+
+1. Close and reopen the solution in Visual Studio
+2. Run `dotnet restore && dotnet build`
+
+### ArtifactsPath is not set
+
+The solution must use artifact output. Add to `Directory.Build.props`:
+
+```xml
+
+ true
+ $(MSBuildThisFileDirectory).artifacts/
+
+```