From 0a45a2c7db77eafc3d301dbe0499d280ac491d82 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 05:22:48 +0000 Subject: [PATCH 1/5] Initial plan From c6c6899d96b17feae4424e9a87be72d2681af260 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 05:32:00 +0000 Subject: [PATCH 2/5] feat(scribe): add solution-local analyzer support (auto-pack & inject) Agent-Logs-Url: https://github.com/BulletsForHumanity/Scribe/sessions/ea27be1c-a52e-4dac-aa4f-6fac868887c5 Co-authored-by: crazycrank <11194014+crazycrank@users.noreply.github.com> --- README.md | 2 + Scribe.Sdk/README.md | 18 ++ Scribe.Sdk/Scribe.Sdk.csproj | 6 + Scribe.Sdk/Sdk/Sdk.props | 3 + Scribe.Sdk/Sdk/Sdk.targets | 3 + Scribe/build/Scribe.SolutionAnalyzer.props | 83 ++++++ Scribe/build/Scribe.SolutionAnalyzer.targets | 46 ++++ docs/architecture-infrastructure.md | 60 ++++- docs/project-setup.md | 28 ++ docs/solution-local-analyzers.md | 254 +++++++++++++++++++ 10 files changed, 500 insertions(+), 3 deletions(-) create mode 100644 Scribe/build/Scribe.SolutionAnalyzer.props create mode 100644 Scribe/build/Scribe.SolutionAnalyzer.targets create mode 100644 docs/solution-local-analyzers.md 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..9b8e199 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: + +```xml + + + true + + + + + + +``` + +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..2146d66 100644 --- a/Scribe.Sdk/Scribe.Sdk.csproj +++ b/Scribe.Sdk/Scribe.Sdk.csproj @@ -38,6 +38,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..ca974c5 --- /dev/null +++ b/Scribe/build/Scribe.SolutionAnalyzer.props @@ -0,0 +1,83 @@ + + + + + + + 0.0.0-local + 0.0.0-local + + + true + true + + + false + + + + + + $(SolutionDir).packages\ + $(MSBuildProjectDirectory)\..\.packages\ + + + + $(ScribeSolutionPackagesDir) + + + + + + + $(RestoreAdditionalProjectSources);$(ScribeSolutionPackagesDir) + + + diff --git a/Scribe/build/Scribe.SolutionAnalyzer.targets b/Scribe/build/Scribe.SolutionAnalyzer.targets new file mode 100644 index 0000000..decfb80 --- /dev/null +++ b/Scribe/build/Scribe.SolutionAnalyzer.targets @@ -0,0 +1,46 @@ + + + + + + + + <_ScribeSaCacheDir>$(NuGetPackageRoot)$([System.String]::Copy('$(PackageId)').ToLowerInvariant())\$(PackageVersion)\ + <_ScribeSaNupkgPath>$(ScribeSolutionPackagesDir)$(PackageId).$(PackageVersion).nupkg + + + + + + + + + + + diff --git a/docs/architecture-infrastructure.md b/docs/architecture-infrastructure.md index 29f75ab..063d3fd 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,58 @@ 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 cache invalidation +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. + +For setup instructions, see [Solution-Local Analyzers](solution-local-analyzers.md). + +### File Layout + +``` +Scribe/build/ + Scribe.SolutionAnalyzer.props <- Configuration (early phase) + Scribe.SolutionAnalyzer.targets <- Cache invalidation target (late phase) +``` + +### Props Phase (Early) + +`Scribe.SolutionAnalyzer.props` runs when `ScribeSolutionAnalyzer=true`: + +1. **Locks the version** to `0.0.0-local` — disables NBGV (`NerdbankGitVersioningEnabled=false`) +2. **Enables auto-pack** via `GeneratePackageOnBuild=true` +3. **Resolves the package directory** — defaults to `.packages/` at the solution root (via `$(SolutionDir)` or parent of project directory) +4. **Redirects pack output** to `$(ScribeSolutionPackagesDir)` +5. **Registers as NuGet source** via `RestoreAdditionalProjectSources` (for the analyzer project itself) + +### Targets Phase (Late) + +`Scribe.SolutionAnalyzer.targets` defines one target: + +**`_ScribeSolutionAnalyzerClearCache`** — Runs before `GenerateNuspec` (which precedes Pack). Removes: +- The cached package extraction from the NuGet global packages folder (`~/.nuget/packages//0.0.0-local/`) +- The old `.nupkg` from the `.packages/` directory + +This ensures consuming projects always pick up the freshly-built package without requiring unique version numbers. + +### Relationship to LocalDev + +Solution-Local Analyzer and LocalDev are complementary: + +| Concern | LocalDev | Solution-Local Analyzer | +|---------|----------|------------------------| +| Scope | Cross-repo | Intra-solution | +| Trigger | `.localscribe` sentinel | `ScribeSolutionAnalyzer=true` | +| Version | NBGV + timestamp suffix | Fixed `0.0.0-local` | +| Override files | Generated `.Directory.Packages.targets` | None needed | +| Package directory | Shared `/.artifacts/packages/` | Solution-local `.packages/` | + +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..fc57337 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: + +```xml + +``` + +See [Solution-Local Analyzers](solution-local-analyzers.md) for the complete setup guide, including NuGet source registration and troubleshooting. + +--- + ## Packaging ### Placing analyzer DLLs diff --git a/docs/solution-local-analyzers.md b/docs/solution-local-analyzers.md new file mode 100644 index 0000000..02f5527 --- /dev/null +++ b/docs/solution-local-analyzers.md @@ -0,0 +1,254 @@ +# 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 a solution-local `.packages/` directory +2. **Uses a fixed local version** (`0.0.0-local`) — no version management needed +3. **Clears the NuGet cache** — consuming projects always get the freshly-built package +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. Register the local package source + +Add a `NuGet.config` at the solution root (one-time setup): + +```xml + + + + + + + +``` + +Alternatively, add to your `Directory.Build.props`: + +```xml + + + $(RestoreAdditionalProjectSources);$(MSBuildThisFileDirectory).packages + + +``` + +### 3. Reference from consuming projects + +```xml + + + +``` + +Or with [Central Package Management](https://learn.microsoft.com/en-us/nuget/consume-packages/central-package-management): + +```xml + + +``` + +### 4. Add `.packages/` to `.gitignore` + +```gitignore +.packages/ +``` + +### 5. Build + +```shell +dotnet build MySolution.sln +``` + +The analyzer auto-packs on build. Consuming projects resolve the package from the local `.packages/` 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 the `.packages/` directory. Every build produces a fresh `.nupkg`. + +### Fixed Version + +The version is locked to `0.0.0-local`. This avoids version management complexity — there's no NBGV, no timestamps, no override files. Consuming projects always reference `Version="0.0.0-local"`. + +### Cache Invalidation + +NuGet caches extracted packages in the global packages folder (`~/.nuget/packages/`). With a fixed version, the cache would serve stale content. The SDK handles this automatically: before each pack, the `_ScribeSolutionAnalyzerClearCache` target removes the cached extraction and the old `.nupkg`, forcing NuGet to re-extract the freshly-built package on the next restore. + +### Package Directory + +By default, packages are placed in `.packages/` relative to the solution directory. The SDK resolves the location in this order: + +1. `$(ScribeSolutionPackagesDir)` — if explicitly set by the developer +2. `$(SolutionDir).packages\` — when building via a solution file or from Visual Studio +3. `$(MSBuildProjectDirectory)\..\.packages\` — fallback for individual project builds + +Override the default in your `Directory.Build.props`: + +```xml + + $(MSBuildThisFileDirectory).packages\ + +``` + +--- + +## Solution Layout + +A typical solution with a solution-local analyzer: + +``` +MySolution/ + MySolution.sln + NuGet.config ← registers .packages/ as a source + Directory.Build.props ← (optional) shared properties + .gitignore ← includes .packages/ + .packages/ ← auto-generated, git-ignored + MyAnalyzer.0.0.0-local.nupkg + MyAnalyzer/ + MyAnalyzer.csproj ← ScribeSolutionAnalyzer=true + MyGenerator.cs + MyApp/ + MyApp.csproj ← + Program.cs +``` + +--- + +## Multiple Analyzers + +A solution can have multiple solution-local analyzers. All pack to the same `.packages/` directory: + +```xml + + + + true + + ... + +``` + +```xml + + + + true + + ... + +``` + +```xml + + + + + +``` + +--- + +## Relationship to LocalDev + +Solution-local analyzers and LocalDev solve different problems: + +| 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 | Fixed `0.0.0-local` | +| Package directory | Shared `/.artifacts/packages/` | Solution-local `.packages/` | +| Setup complexity | `$(ScribeRoot)`, trigger project, package names | One property + NuGet source | + +If your analyzer is (or will become) a standalone package consumed across repositories, use the standard Scribe SDK workflow with [LocalDev](project-setup.md#local-development-localdev). If it exists solely to serve the solution it lives in, use `ScribeSolutionAnalyzer`. + +--- + +## Provided Properties + +| Property | Value | Set by | +|----------|-------|--------| +| `$(ScribeSolutionAnalyzer)` | `true` | Developer (in `.csproj`) | +| `$(ScribeSolutionPackagesDir)` | `.packages/` at solution root | SDK (overridable) | +| `$(Version)` | `0.0.0-local` | SDK | +| `$(PackageVersion)` | `0.0.0-local` | SDK | +| `$(GeneratePackageOnBuild)` | `true` | SDK | +| `$(PackageOutputPath)` | `$(ScribeSolutionPackagesDir)` | SDK | + +--- + +## Design Decisions + +| Decision | Choice | Rationale | +|----------|--------|-----------| +| Detection mechanism | Explicit `ScribeSolutionAnalyzer=true` property | Simple, discoverable, no magic. Convention-based detection would be ambiguous. | +| Version strategy | Fixed `0.0.0-local` | Avoids version management complexity. Cache invalidation is handled by a build target. | +| Package directory | `.packages/` at solution root | Mirrors `.artifacts/` convention. Solution-scoped, git-ignored. | +| NuGet source registration | Manual one-time setup (NuGet.config or Directory.Build.props) | The SDK can only configure projects that use it. Consuming projects need a standard NuGet mechanism. | +| Cache invalidation | Delete from NuGet global cache before each pack | Ensures consuming projects always get the fresh package without requiring unique versions. | + +--- + +## Troubleshooting + +### Consuming project doesn't see the analyzer + +1. Verify the `.packages/` directory contains a `.nupkg` for the analyzer +2. Verify `NuGet.config` (or `RestoreAdditionalProjectSources`) includes the `.packages/` directory +3. Run `dotnet restore` on the consuming project +4. In Visual Studio, try **Build → Rebuild Solution** + +### Stale generated code after analyzer changes + +The `_ScribeSolutionAnalyzerClearCache` target should handle this automatically. If you still see stale output: + +1. Delete the `.packages/` directory +2. Delete `~/.nuget/packages//0.0.0-local/` +3. Run `dotnet restore && dotnet build` + +### Package directory location is wrong + +Set `$(ScribeSolutionPackagesDir)` explicitly in your `Directory.Build.props`: + +```xml + + $(MSBuildThisFileDirectory).packages\ + +``` From 26b612b85908861bbbdc21a4fbfe2cfc13267a86 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 05:48:15 +0000 Subject: [PATCH 3/5] refactor(scribe): use timestamp version and override files for solution-local analyzers Agent-Logs-Url: https://github.com/BulletsForHumanity/Scribe/sessions/a0a87e65-138d-464f-bb3a-d0393d5bfd40 Co-authored-by: crazycrank <11194014+crazycrank@users.noreply.github.com> --- Scribe.Sdk/README.md | 4 +- Scribe/build/Scribe.SolutionAnalyzer.props | 85 +++++----- Scribe/build/Scribe.SolutionAnalyzer.targets | 62 ++++--- docs/architecture-infrastructure.md | 38 +++-- docs/project-setup.md | 6 +- docs/solution-local-analyzers.md | 168 ++++++++++--------- 6 files changed, 197 insertions(+), 166 deletions(-) diff --git a/Scribe.Sdk/README.md b/Scribe.Sdk/README.md index 9b8e199..507a9a7 100644 --- a/Scribe.Sdk/README.md +++ b/Scribe.Sdk/README.md @@ -82,7 +82,7 @@ The SDK includes [LocalDev](https://github.com/BulletsForHumanity/Scribe/blob/ma ## Solution-Local Analyzers -For analyzers that live inside the same solution they serve, set `ScribeSolutionAnalyzer=true` to auto-pack on build: +For analyzers that live inside the same solution they serve, set `ScribeSolutionAnalyzer=true` to auto-pack on build with automatic version management: ```xml @@ -96,7 +96,7 @@ For analyzers that live inside the same solution they serve, set `ScribeSolution ``` -See [Solution-Local Analyzers](https://github.com/BulletsForHumanity/Scribe/blob/master/docs/solution-local-analyzers.md) for the complete setup guide. +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 diff --git a/Scribe/build/Scribe.SolutionAnalyzer.props b/Scribe/build/Scribe.SolutionAnalyzer.props index ca974c5..b941d47 100644 --- a/Scribe/build/Scribe.SolutionAnalyzer.props +++ b/Scribe/build/Scribe.SolutionAnalyzer.props @@ -3,9 +3,13 @@ Scribe Solution-Local Analyzer — Property Definitions (Early Phase) Automatically activates when a Scribe SDK project sets - ScribeSolutionAnalyzer=true. Configures auto-pack on build, outputs to a - solution-local .packages/ directory, and registers that directory as a - NuGet source. + ScribeSolutionAnalyzer=true. Configures auto-pack on build with a + timestamp-based version, outputs to the solution's artifacts/packages/ + directory, and registers that directory as a NuGet source. + + Uses the same mechanism as LocalDev — the companion targets file + generates a version override .Directory.Packages.targets file so + consuming projects resolve the locally-built package automatically. ── Usage ──────────────────────────────────────────────────────────────── @@ -15,69 +19,72 @@ true - Consuming projects reference the analyzer via standard PackageReference: + Consuming projects reference the analyzer via standard PackageReference. + The version is managed automatically via an override file — consumers + only need to declare the PackageVersion Include in Directory.Packages.props + (any version; the override file will pin the actual version). - + ── Prerequisites ────────────────────────────────────────────────────── - A NuGet.config or Directory.Build.props must register the .packages/ - directory as a package source. See docs/solution-local-analyzers.md. + The solution must use artifact output (UseArtifactsOutput=true) with + $(ArtifactsPath) set in Directory.Build.props. - ── Provides ───────────────────────────────────────────────────────────── + The solution's Directory.Build.props must register the packages source: + + + $(RestoreAdditionalProjectSources);$(ArtifactsPath)packages + + + The solution's Directory.Build.targets must import override files: + + - $(ScribeSolutionAnalyzer) 'true' when active. - $(ScribeSolutionPackagesDir) Path to the solution-local .packages/ directory. + See docs/solution-local-analyzers.md for the complete setup guide. - ── Customisation ──────────────────────────────────────────────────────── + ── Provides ───────────────────────────────────────────────────────────── - $(ScribeSolutionPackagesDir) Override the default .packages/ location. + $(ScribeSolutionAnalyzer) 'true' when active. + $(Version) Timestamp-based: 0.0.0-dev.yyyyMMddHHmmss + $(PackageVersion) Same as $(Version). --> - - 0.0.0-local - 0.0.0-local + + <_ScribeSaTimestamp>$([System.DateTime]::UtcNow.ToString('yyyyMMddHHmmss')) + 0.0.0-dev.$(_ScribeSaTimestamp) + 0.0.0-dev.$(_ScribeSaTimestamp) true true - + false - - $(SolutionDir).packages\ - $(MSBuildProjectDirectory)\..\.packages\ - - - - $(ScribeSolutionPackagesDir) + + $(ArtifactsPath)packages\ - + - $(RestoreAdditionalProjectSources);$(ScribeSolutionPackagesDir) + $(RestoreAdditionalProjectSources);$(ArtifactsPath)packages diff --git a/Scribe/build/Scribe.SolutionAnalyzer.targets b/Scribe/build/Scribe.SolutionAnalyzer.targets index decfb80..211ee1d 100644 --- a/Scribe/build/Scribe.SolutionAnalyzer.targets +++ b/Scribe/build/Scribe.SolutionAnalyzer.targets @@ -2,45 +2,55 @@ - + - <_ScribeSaCacheDir>$(NuGetPackageRoot)$([System.String]::Copy('$(PackageId)').ToLowerInvariant())\$(PackageVersion)\ - <_ScribeSaNupkgPath>$(ScribeSolutionPackagesDir)$(PackageId).$(PackageVersion).nupkg + <_ScribeSaVersion>$(PackageVersion) + <_ScribeSaOverridePath>$(ArtifactsPath)$(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 063d3fd..d24e336 100644 --- a/docs/architecture-infrastructure.md +++ b/docs/architecture-infrastructure.md @@ -59,14 +59,14 @@ 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.SolutionAnalyzer.targets`** for solution-local cache invalidation +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. +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). @@ -75,40 +75,48 @@ For setup instructions, see [Solution-Local Analyzers](solution-local-analyzers. ``` Scribe/build/ Scribe.SolutionAnalyzer.props <- Configuration (early phase) - Scribe.SolutionAnalyzer.targets <- Cache invalidation target (late phase) + Scribe.SolutionAnalyzer.targets <- Override file generation (late phase) ``` ### Props Phase (Early) `Scribe.SolutionAnalyzer.props` runs when `ScribeSolutionAnalyzer=true`: -1. **Locks the version** to `0.0.0-local` — disables NBGV (`NerdbankGitVersioningEnabled=false`) -2. **Enables auto-pack** via `GeneratePackageOnBuild=true` -3. **Resolves the package directory** — defaults to `.packages/` at the solution root (via `$(SolutionDir)` or parent of project directory) -4. **Redirects pack output** to `$(ScribeSolutionPackagesDir)` +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. **Redirects pack output** to `$(ArtifactsPath)packages/` 5. **Registers as NuGet source** via `RestoreAdditionalProjectSources` (for the analyzer project itself) ### Targets Phase (Late) `Scribe.SolutionAnalyzer.targets` defines one target: -**`_ScribeSolutionAnalyzerClearCache`** — Runs before `GenerateNuspec` (which precedes Pack). Removes: -- The cached package extraction from the NuGet global packages folder (`~/.nuget/packages//0.0.0-local/`) -- The old `.nupkg` from the `.packages/` directory +**`_ScribeSolutionAnalyzerOverride`** — Runs after Pack. Generates a `.Directory.Packages.targets` file in `$(ArtifactsPath)` containing a `PackageVersion Update` entry: -This ensures consuming projects always pick up the freshly-built package without requiring unique version numbers. +```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 are complementary: +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 | Fixed `0.0.0-local` | -| Override files | Generated `.Directory.Packages.targets` | None needed | -| Package directory | Shared `/.artifacts/packages/` | Solution-local `.packages/` | +| 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 fc57337..0b23244 100644 --- a/docs/project-setup.md +++ b/docs/project-setup.md @@ -161,13 +161,13 @@ Set `ScribeSolutionAnalyzer=true` in your analyzer `.csproj`: ``` -Consuming projects reference it like any NuGet package: +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 NuGet source registration and troubleshooting. +See [Solution-Local Analyzers](solution-local-analyzers.md) for the complete setup guide, including consumer-side `Directory.Build.props` and `.targets` configuration. --- diff --git a/docs/solution-local-analyzers.md b/docs/solution-local-analyzers.md index 02f5527..84986da 100644 --- a/docs/solution-local-analyzers.md +++ b/docs/solution-local-analyzers.md @@ -16,9 +16,9 @@ Examples: 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 a solution-local `.packages/` directory -2. **Uses a fixed local version** (`0.0.0-local`) — no version management needed -3. **Clears the NuGet cache** — consuming projects always get the freshly-built package +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. @@ -41,58 +41,55 @@ Consuming projects reference the analyzer via standard ``, get ``` -### 2. Register the local package source +### 2. Configure the solution for local packages -Add a `NuGet.config` at the solution root (one-time setup): +The solution must use artifact output. In `Directory.Build.props`: ```xml - - - - - - - -``` - -Alternatively, add to your `Directory.Build.props`: + + true + $(MSBuildThisFileDirectory).artifacts/ + -```xml + - $(RestoreAdditionalProjectSources);$(MSBuildThisFileDirectory).packages + $(RestoreAdditionalProjectSources);$(ArtifactsPath)packages ``` -### 3. Reference from consuming projects +In `Directory.Build.targets`, import override files generated by solution-local analyzers: ```xml - - - + ``` -Or with [Central Package Management](https://learn.microsoft.com/en-us/nuget/consume-packages/central-package-management): +### 3. Reference from consuming projects + +Add the analyzer to your `Directory.Packages.props` (any version — the override file pins the actual version): ```xml - + ``` -### 4. Add `.packages/` to `.gitignore` +Then reference it from consuming projects: -```gitignore -.packages/ +```xml + + + ``` -### 5. Build +### 4. Build ```shell dotnet build MySolution.sln ``` -The analyzer auto-packs on build. Consuming projects resolve the package from the local `.packages/` directory. Diagnostics and generated code appear automatically. +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. --- @@ -100,31 +97,33 @@ The analyzer auto-packs on build. Consuming projects resolve the package from th ### Auto-Pack on Build -When `ScribeSolutionAnalyzer=true`, the SDK sets `GeneratePackageOnBuild=true` and redirects `PackageOutputPath` to the `.packages/` directory. Every build produces a fresh `.nupkg`. +When `ScribeSolutionAnalyzer=true`, the SDK sets `GeneratePackageOnBuild=true` and redirects `PackageOutputPath` to `$(ArtifactsPath)packages/`. Every build produces a fresh `.nupkg`. -### Fixed Version +### Timestamp Version -The version is locked to `0.0.0-local`. This avoids version management complexity — there's no NBGV, no timestamps, no override files. Consuming projects always reference `Version="0.0.0-local"`. +Each build produces a unique version: `0.0.0-dev.yyyyMMddHHmmss` (e.g. `0.0.0-dev.20260406054112`). This serves two purposes: -### Cache Invalidation +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 -NuGet caches extracted packages in the global packages folder (`~/.nuget/packages/`). With a fixed version, the cache would serve stale content. The SDK handles this automatically: before each pack, the `_ScribeSolutionAnalyzerClearCache` target removes the cached extraction and the old `.nupkg`, forcing NuGet to re-extract the freshly-built package on the next restore. +### Version Override File -### Package Directory +After each pack, the SDK generates a `.Directory.Packages.targets` file in the artifacts directory: -By default, packages are placed in `.packages/` relative to the solution directory. The SDK resolves the location in this order: +```xml + + + + + + +``` -1. `$(ScribeSolutionPackagesDir)` — if explicitly set by the developer -2. `$(SolutionDir).packages\` — when building via a solution file or from Visual Studio -3. `$(MSBuildProjectDirectory)\..\.packages\` — fallback for individual project builds +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. -Override the default in your `Directory.Build.props`: +### Package Directory -```xml - - $(MSBuildThisFileDirectory).packages\ - -``` +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. --- @@ -135,16 +134,19 @@ A typical solution with a solution-local analyzer: ``` MySolution/ MySolution.sln - NuGet.config ← registers .packages/ as a source - Directory.Build.props ← (optional) shared properties - .gitignore ← includes .packages/ - .packages/ ← auto-generated, git-ignored - MyAnalyzer.0.0.0-local.nupkg + 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 ← + MyApp.csproj ← Program.cs ``` @@ -152,7 +154,7 @@ MySolution/ ## Multiple Analyzers -A solution can have multiple solution-local analyzers. All pack to the same `.packages/` directory: +A solution can have multiple solution-local analyzers. Each generates its own override file in the artifacts directory: ```xml @@ -174,30 +176,34 @@ A solution can have multiple solution-local analyzers. All pack to the same `.pa ``` -```xml - - - - - +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 and LocalDev solve different problems: +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 | Fixed `0.0.0-local` | -| Package directory | Shared `/.artifacts/packages/` | Solution-local `.packages/` | -| Setup complexity | `$(ScribeRoot)`, trigger project, package names | One property + NuGet source | +| 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 | -If your analyzer is (or will become) a standalone package consumed across repositories, use the standard Scribe SDK workflow with [LocalDev](project-setup.md#local-development-localdev). If it exists solely to serve the solution it lives in, use `ScribeSolutionAnalyzer`. +Both features are independent and can coexist. A solution can have solution-local analyzers and also participate in a LocalDev chain. --- @@ -206,11 +212,10 @@ If your analyzer is (or will become) a standalone package consumed across reposi | Property | Value | Set by | |----------|-------|--------| | `$(ScribeSolutionAnalyzer)` | `true` | Developer (in `.csproj`) | -| `$(ScribeSolutionPackagesDir)` | `.packages/` at solution root | SDK (overridable) | -| `$(Version)` | `0.0.0-local` | SDK | -| `$(PackageVersion)` | `0.0.0-local` | SDK | +| `$(Version)` | `0.0.0-dev.yyyyMMddHHmmss` | SDK (timestamp-based) | +| `$(PackageVersion)` | `0.0.0-dev.yyyyMMddHHmmss` | SDK (same as Version) | | `$(GeneratePackageOnBuild)` | `true` | SDK | -| `$(PackageOutputPath)` | `$(ScribeSolutionPackagesDir)` | SDK | +| `$(PackageOutputPath)` | `$(ArtifactsPath)packages\` | SDK | --- @@ -219,10 +224,10 @@ If your analyzer is (or will become) a standalone package consumed across reposi | Decision | Choice | Rationale | |----------|--------|-----------| | Detection mechanism | Explicit `ScribeSolutionAnalyzer=true` property | Simple, discoverable, no magic. Convention-based detection would be ambiguous. | -| Version strategy | Fixed `0.0.0-local` | Avoids version management complexity. Cache invalidation is handled by a build target. | -| Package directory | `.packages/` at solution root | Mirrors `.artifacts/` convention. Solution-scoped, git-ignored. | -| NuGet source registration | Manual one-time setup (NuGet.config or Directory.Build.props) | The SDK can only configure projects that use it. Consuming projects need a standard NuGet mechanism. | -| Cache invalidation | Delete from NuGet global cache before each pack | Ensures consuming projects always get the fresh package without requiring unique versions. | +| 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. | --- @@ -230,25 +235,26 @@ If your analyzer is (or will become) a standalone package consumed across reposi ### Consuming project doesn't see the analyzer -1. Verify the `.packages/` directory contains a `.nupkg` for the analyzer -2. Verify `NuGet.config` (or `RestoreAdditionalProjectSources`) includes the `.packages/` directory -3. Run `dotnet restore` on the consuming project -4. In Visual Studio, try **Build → Rebuild Solution** +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 -The `_ScribeSolutionAnalyzerClearCache` target should handle this automatically. If you still see stale output: +Each build produces a unique timestamp version, which forces Visual Studio to reload the analyzer. If you still see stale output: -1. Delete the `.packages/` directory -2. Delete `~/.nuget/packages//0.0.0-local/` -3. Run `dotnet restore && dotnet build` +1. Close and reopen the solution in Visual Studio +2. Run `dotnet restore && dotnet build` -### Package directory location is wrong +### ArtifactsPath is not set -Set `$(ScribeSolutionPackagesDir)` explicitly in your `Directory.Build.props`: +The solution must use artifact output. Add to `Directory.Build.props`: ```xml - $(MSBuildThisFileDirectory).packages\ + true + $(MSBuildThisFileDirectory).artifacts/ ``` From c22453b9df38e8fdad5efdd7cafd6f4a8392b9f5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 06:31:45 +0000 Subject: [PATCH 4/5] docs(scribe): mention solution-local analyzers in SDK package description Agent-Logs-Url: https://github.com/BulletsForHumanity/Scribe/sessions/65805b99-d304-4fa6-aa9e-c36044fb2184 Co-authored-by: crazycrank <11194014+crazycrank@users.noreply.github.com> --- Scribe.Sdk/Scribe.Sdk.csproj | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Scribe.Sdk/Scribe.Sdk.csproj b/Scribe.Sdk/Scribe.Sdk.csproj index 2146d66..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 From 60484f32ee5a4365afa1ebdbe748d724209e0da3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 07:12:11 +0000 Subject: [PATCH 5/5] fix(scribe): normalise ArtifactsPath separator and prefer NuGetPackageVersion Agent-Logs-Url: https://github.com/BulletsForHumanity/Scribe/sessions/3989b1e3-e54b-418a-ad37-33ad71a09acf Co-authored-by: crazycrank <11194014+crazycrank@users.noreply.github.com> --- Scribe/build/Scribe.SolutionAnalyzer.props | 17 +++++++++++++---- Scribe/build/Scribe.SolutionAnalyzer.targets | 8 +++++--- docs/architecture-infrastructure.md | 7 ++++--- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/Scribe/build/Scribe.SolutionAnalyzer.props b/Scribe/build/Scribe.SolutionAnalyzer.props index b941d47..5f7b01e 100644 --- a/Scribe/build/Scribe.SolutionAnalyzer.props +++ b/Scribe/build/Scribe.SolutionAnalyzer.props @@ -66,14 +66,23 @@ false + + + + <_ScribeSaArtifactsDir>$([MSBuild]::EnsureTrailingSlash('$(ArtifactsPath)')) + + - - $(ArtifactsPath)packages\ + + $(_ScribeSaArtifactsDir)packages\ @@ -82,9 +91,9 @@ analyzer project itself (so it can resolve sibling solution-local packages). Consuming projects must register this source in Directory.Build.props. --> - + - $(RestoreAdditionalProjectSources);$(ArtifactsPath)packages + $(RestoreAdditionalProjectSources);$(_ScribeSaArtifactsDir)packages diff --git a/Scribe/build/Scribe.SolutionAnalyzer.targets b/Scribe/build/Scribe.SolutionAnalyzer.targets index 211ee1d..8e83133 100644 --- a/Scribe/build/Scribe.SolutionAnalyzer.targets +++ b/Scribe/build/Scribe.SolutionAnalyzer.targets @@ -28,8 +28,10 @@ and '$(ArtifactsPath)' != ''" > - <_ScribeSaVersion>$(PackageVersion) - <_ScribeSaOverridePath>$(ArtifactsPath)$(PackageId).Directory.Packages.targets + <_ScribeSaVersion>$(NuGetPackageVersion) + <_ScribeSaVersion Condition="'$(_ScribeSaVersion)' == ''">$(PackageVersion) + <_ScribeSaArtifactsDir>$([MSBuild]::EnsureTrailingSlash('$(ArtifactsPath)')) + <_ScribeSaOverridePath>$(_ScribeSaArtifactsDir)$(PackageId).Directory.Packages.targets @@ -41,7 +43,7 @@ <_ScribeSaLine Include="</Project>" /> - +