diff --git a/.github/workflows/official-build.yaml b/.github/workflows/official-build.yaml
index c240aa1..5158e8c 100644
--- a/.github/workflows/official-build.yaml
+++ b/.github/workflows/official-build.yaml
@@ -35,6 +35,14 @@ jobs:
run: dotnet test --verbosity normal
working-directory: src/ReferenceCop.MSBuild.Tests
+ - name: Pack ReferenceCop for playground
+ run: dotnet pack -c Debug --nologo
+ working-directory: src/ReferenceCop.Package
+
+ - name: Run end-to-end tests
+ run: ./playground/Run-E2ETests.ps1
+ shell: pwsh
+
- name: Set NuGet Package Version Environment Variable from tag (only for tagged builds)
if: ${{ github.ref_type == 'tag' }}
run: echo "PKGVERSION=$('${{ github.ref_name }}'.replace('v',''))" >> $env:GITHUB_ENV
diff --git a/.github/workflows/pr-build.yaml b/.github/workflows/pr-build.yaml
index 1a78a27..d00b945 100644
--- a/.github/workflows/pr-build.yaml
+++ b/.github/workflows/pr-build.yaml
@@ -33,4 +33,12 @@ jobs:
- name: Run Tests in ReferenceCop.MSBuild.Tests
run: dotnet test --verbosity normal
- working-directory: src/ReferenceCop.MSBuild.Tests
\ No newline at end of file
+ working-directory: src/ReferenceCop.MSBuild.Tests
+
+ - name: Pack ReferenceCop for playground
+ run: dotnet pack -c Release --nologo -p:IsDevEnvironment=true
+ working-directory: src/ReferenceCop.Package
+
+ - name: Run end-to-end tests
+ run: ./playground/Run-E2ETests.ps1
+ shell: pwsh
\ No newline at end of file
diff --git a/playground/Run-E2ETests.ps1 b/playground/Run-E2ETests.ps1
new file mode 100644
index 0000000..acf6614
--- /dev/null
+++ b/playground/Run-E2ETests.ps1
@@ -0,0 +1,222 @@
+<#
+.SYNOPSIS
+ Automated end-to-end test runner for ReferenceCop playground scenarios.
+
+.DESCRIPTION
+ Builds various test configurations in the playground and validates that
+ ReferenceCop correctly detects violations and allows valid references.
+
+.PARAMETER PackFirst
+ If set, builds and packs the ReferenceCop package before running tests.
+#>
+param(
+ [switch]$PackFirst
+)
+
+$ErrorActionPreference = "Stop"
+$script:TestsPassed = 0
+$script:TestsFailed = 0
+$script:Failures = @()
+
+$RepoRoot = (Resolve-Path "$PSScriptRoot/..").Path
+$PlaygroundRoot = "$PSScriptRoot/TestProject"
+
+function Write-TestHeader($name) {
+ Write-Host "`n========================================" -ForegroundColor Cyan
+ Write-Host " TEST: $name" -ForegroundColor Cyan
+ Write-Host "========================================" -ForegroundColor Cyan
+}
+
+function Assert-BuildFails($project, $testName, $expectedDiagnostic) {
+ Write-TestHeader $testName
+ $output = dotnet build "$PlaygroundRoot/$project" --no-restore 2>&1 | Out-String
+ $exitCode = $LASTEXITCODE
+
+ if ($exitCode -eq 0) {
+ Write-Host " FAIL: Build succeeded but was expected to fail" -ForegroundColor Red
+ Write-Host " Output: $output" -ForegroundColor Gray
+ $script:TestsFailed++
+ $script:Failures += $testName
+ return
+ }
+
+ if ($expectedDiagnostic -and ($output -notmatch $expectedDiagnostic)) {
+ Write-Host " FAIL: Build failed but diagnostic '$expectedDiagnostic' not found in output" -ForegroundColor Red
+ Write-Host " Output: $output" -ForegroundColor Gray
+ $script:TestsFailed++
+ $script:Failures += $testName
+ return
+ }
+
+ Write-Host " PASS: Build failed as expected with diagnostic '$expectedDiagnostic'" -ForegroundColor Green
+ $script:TestsPassed++
+}
+
+function Assert-BuildSucceeds($project, $testName) {
+ Write-TestHeader $testName
+ $output = dotnet build "$PlaygroundRoot/$project" --no-restore 2>&1 | Out-String
+ $exitCode = $LASTEXITCODE
+
+ if ($exitCode -ne 0) {
+ Write-Host " FAIL: Build failed but was expected to succeed" -ForegroundColor Red
+ Write-Host " Output: $output" -ForegroundColor Gray
+ $script:TestsFailed++
+ $script:Failures += $testName
+ return
+ }
+
+ Write-Host " PASS: Build succeeded as expected" -ForegroundColor Green
+ $script:TestsPassed++
+}
+
+# --- Setup ---
+
+if ($PackFirst) {
+ Write-Host "`nPacking ReferenceCop..." -ForegroundColor Yellow
+ Push-Location "$RepoRoot/src/ReferenceCop.Package"
+ dotnet pack -c Debug --nologo -v quiet
+ if ($LASTEXITCODE -ne 0) {
+ Write-Host "FATAL: Failed to pack ReferenceCop" -ForegroundColor Red
+ exit 1
+ }
+ Pop-Location
+}
+
+# Restore all playground projects
+Write-Host "`nRestoring playground projects..." -ForegroundColor Yellow
+dotnet restore "$PlaygroundRoot/SampleApp/SampleApp.csproj" --nologo -v quiet
+dotnet restore "$PlaygroundRoot/ValidApp/ValidApp.csproj" --nologo -v quiet
+
+# --- Test Scenarios ---
+
+# Scenario 1: AssemblyName rule - SampleLibrary references Newtonsoft.Json which is blocked
+Assert-BuildFails "SampleLibrary" `
+ "AssemblyName rule: Newtonsoft.Json blocked" `
+ "RC000[12]"
+
+# Scenario 2: ProjectPath rule - SampleApp referencing InternalLib should be blocked
+# We need to temporarily add a project reference for this test
+$sampleAppCsproj = "$PlaygroundRoot/SampleApp/SampleApp.csproj"
+$originalContent = Get-Content $sampleAppCsproj -Raw
+
+# Add InternalLib reference
+$modifiedContent = $originalContent -replace '(\s*)', @"
+
+
+
+
+
+
+
+"@
+# Fix: replace last properly
+$modifiedContent = $originalContent -replace '(\s*
+
+
+
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+ App
+
+
+
+
+
+
+
+
+"@
+
+# Save originals, write modified project and minimal program
+$originalSampleApp = Get-Content $sampleAppCsproj -Raw
+$sampleAppProgram = "$PlaygroundRoot/SampleApp/Program.cs"
+$originalProgram = Get-Content $sampleAppProgram -Raw
+$minimalProgram = "namespace SampleApp;`npublic class Program { public static void Main() { } }"
+
+Set-Content $sampleAppCsproj -Value $testCsproj -NoNewline
+Set-Content $sampleAppProgram -Value $minimalProgram -NoNewline
+dotnet restore "$sampleAppCsproj" --nologo -v quiet 2>$null
+
+Assert-BuildFails "SampleApp" `
+ "ProjectPath rule: App cannot reference Internal" `
+ "RC000[12]"
+
+# Restore originals
+Set-Content $sampleAppCsproj -Value $originalSampleApp -NoNewline
+Set-Content $sampleAppProgram -Value $originalProgram -NoNewline
+dotnet restore "$sampleAppCsproj" --nologo -v quiet 2>$null
+
+# Scenario 3: ProjectTag rule - App referencing Tools project should warn
+$toolsTestCsproj = @"
+
+
+ Exe
+ net8.0
+ enable
+ enable
+ App
+
+
+
+
+
+
+
+
+"@
+
+Set-Content $sampleAppCsproj -Value $toolsTestCsproj -NoNewline
+Set-Content $sampleAppProgram -Value $minimalProgram -NoNewline
+dotnet restore "$sampleAppCsproj" --nologo -v quiet 2>$null
+
+# ProjectTag rule has Severity=Warning, so build may succeed but with warnings
+# We check for the diagnostic in output regardless of exit code
+Write-TestHeader "ProjectTag rule: App cannot reference Tools (warning)"
+$output = dotnet build "$PlaygroundRoot/SampleApp" --no-restore 2>&1 | Out-String
+if ($output -match "RC000[12]") {
+ Write-Host " PASS: ProjectTag violation diagnostic detected" -ForegroundColor Green
+ $script:TestsPassed++
+} else {
+ Write-Host " FAIL: Expected ProjectTag warning diagnostic not found" -ForegroundColor Red
+ Write-Host " Output: $output" -ForegroundColor Gray
+ $script:TestsFailed++
+ $script:Failures += "ProjectTag rule: App cannot reference Tools (warning)"
+}
+
+# Restore originals
+Set-Content $sampleAppCsproj -Value $originalSampleApp -NoNewline
+Set-Content $sampleAppProgram -Value $originalProgram -NoNewline
+dotnet restore "$sampleAppCsproj" --nologo -v quiet 2>$null
+
+# Scenario 4: Valid project builds successfully (no violations)
+Assert-BuildSucceeds "ValidApp" `
+ "Valid project: No violations, build succeeds"
+
+# --- Summary ---
+Write-Host "`n========================================" -ForegroundColor Cyan
+Write-Host " RESULTS" -ForegroundColor Cyan
+Write-Host "========================================" -ForegroundColor Cyan
+Write-Host " Passed: $script:TestsPassed" -ForegroundColor Green
+Write-Host " Failed: $script:TestsFailed" -ForegroundColor $(if ($script:TestsFailed -gt 0) { "Red" } else { "Green" })
+
+if ($script:TestsFailed -gt 0) {
+ Write-Host "`n Failed tests:" -ForegroundColor Red
+ foreach ($f in $script:Failures) {
+ Write-Host " - $f" -ForegroundColor Red
+ }
+ exit 1
+}
+
+Write-Host "`n All e2e tests passed!" -ForegroundColor Green
+exit 0
diff --git a/playground/TEST-SCENARIOS.md b/playground/TEST-SCENARIOS.md
new file mode 100644
index 0000000..547a080
--- /dev/null
+++ b/playground/TEST-SCENARIOS.md
@@ -0,0 +1,27 @@
+# E2E Test Scenarios
+
+This document describes the automated end-to-end test scenarios run by `Run-E2ETests.ps1`.
+
+## Scenarios
+
+| # | Rule Type | Scenario | Expected Result |
+|---|-----------|----------|-----------------|
+| 1 | AssemblyName | SampleLibrary references Newtonsoft.Json (blocked by `NoNewtonsoftJson` rule) | Build fails with RC0001/RC0002 |
+| 2 | ProjectPath | SampleApp references InternalLib (blocked by `AppCannotReferenceInternal` rule) | Build fails with RC0001/RC0002 |
+| 3 | ProjectTag | SampleApp (tag=App) references ToolsProject (tag=Tools), blocked by `AppCannotReferenceTools` rule | Warning diagnostic RC0001/RC0002 emitted |
+| 4 | Valid | ValidApp with no violations | Build succeeds cleanly |
+
+## Running Locally
+
+```powershell
+# Pack first, then run tests
+./playground/Run-E2ETests.ps1 -PackFirst
+
+# Or if already packed:
+./playground/Run-E2ETests.ps1
+```
+
+## CI Integration
+
+The tests run automatically in both PR and official build workflows after the unit tests pass.
+The workflow packs the package in Debug mode, then invokes the test runner.
diff --git a/playground/TestProject/InternalLib/InternalHelper.cs b/playground/TestProject/InternalLib/InternalHelper.cs
new file mode 100644
index 0000000..ae50d00
--- /dev/null
+++ b/playground/TestProject/InternalLib/InternalHelper.cs
@@ -0,0 +1,6 @@
+namespace InternalLib;
+
+public class InternalHelper
+{
+ public static string GetSecret() => "internal";
+}
diff --git a/playground/TestProject/InternalLib/InternalLib.csproj b/playground/TestProject/InternalLib/InternalLib.csproj
new file mode 100644
index 0000000..6f94e6a
--- /dev/null
+++ b/playground/TestProject/InternalLib/InternalLib.csproj
@@ -0,0 +1,10 @@
+
+
+
+ net8.0
+ enable
+ enable
+ Library
+
+
+
diff --git a/playground/TestProject/ReferenceCop.config b/playground/TestProject/ReferenceCop.config
index dce9c77..b196501 100644
--- a/playground/TestProject/ReferenceCop.config
+++ b/playground/TestProject/ReferenceCop.config
@@ -3,15 +3,7 @@
true
true
-
-
- NoSystemWebReferences
- System.Web assemblies should not be referenced
- Error
- System.Web*
-
-
-
+
AppCannotReferenceTools
Application projects should not reference tool projects
@@ -26,14 +18,14 @@
Applications should not reference internal implementation libraries
Error
**/SampleApp/**
- **/Internal/**
+ **/InternalLib/**
NoNewtonsoftJson
Use System.Text.Json instead of Newtonsoft.Json
- Warning
+ Error
Newtonsoft.Json*
diff --git a/playground/TestProject/ToolsProject/Tool.cs b/playground/TestProject/ToolsProject/Tool.cs
new file mode 100644
index 0000000..ced6dcc
--- /dev/null
+++ b/playground/TestProject/ToolsProject/Tool.cs
@@ -0,0 +1,6 @@
+namespace ToolsProject;
+
+public class Tool
+{
+ public static void Run() { }
+}
diff --git a/playground/TestProject/ToolsProject/ToolsProject.csproj b/playground/TestProject/ToolsProject/ToolsProject.csproj
new file mode 100644
index 0000000..5ca2e66
--- /dev/null
+++ b/playground/TestProject/ToolsProject/ToolsProject.csproj
@@ -0,0 +1,10 @@
+
+
+
+ net8.0
+ enable
+ enable
+ Tools
+
+
+
diff --git a/playground/TestProject/ValidApp/Program.cs b/playground/TestProject/ValidApp/Program.cs
new file mode 100644
index 0000000..8012955
--- /dev/null
+++ b/playground/TestProject/ValidApp/Program.cs
@@ -0,0 +1 @@
+Console.WriteLine("Hello from ValidApp!");
diff --git a/playground/TestProject/ValidApp/ValidApp.csproj b/playground/TestProject/ValidApp/ValidApp.csproj
new file mode 100644
index 0000000..7d79ec2
--- /dev/null
+++ b/playground/TestProject/ValidApp/ValidApp.csproj
@@ -0,0 +1,15 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+ App
+
+
+
+
+
+
+
diff --git a/playground/nuget.config b/playground/nuget.config
index f45e370..3af9dc5 100644
--- a/playground/nuget.config
+++ b/playground/nuget.config
@@ -7,7 +7,7 @@
-
+
diff --git a/playground/playground.slnx b/playground/playground.slnx
index c5ee38d..c0fecb1 100644
--- a/playground/playground.slnx
+++ b/playground/playground.slnx
@@ -7,4 +7,7 @@
+
+
+