From db4a19fac19aa0afd69912e26f1f67e5fa38540b Mon Sep 17 00:00:00 2001
From: devlooped-bot <bot@devlooped.com>
Date: Tue, 1 Oct 2024 01:01:05 +0000
Subject: [PATCH] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Bump=20files=20with=20dotn?=
 =?UTF-8?q?et-file=20sync=20#=20devlooped/oss?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

- Improve triage actions on issues https://github.com/devlooped/oss/commit/33000c0
- SponsorLink code should be checked as regular code https://github.com/devlooped/oss/commit/e81ab75
- Set Version from VersionLabel if it's a refs/tags/ https://github.com/devlooped/oss/commit/57653a2
- Cleanup build and publish to use VersionLabel https://github.com/devlooped/oss/commit/14deaea
- Improve default value for GenerateDocumentationFile https://github.com/devlooped/oss/commit/b76de49

# clarius/pages

# inventaire/isbn3
---
 .editorconfig                                 |   3 -
 .gitattributes                                |  22 +-
 .github/release.yml                           |   2 +-
 .github/workflows/build.yml                   |  31 +-
 .github/workflows/changelog.config            |   2 +-
 .github/workflows/includes.yml                |   2 +-
 .github/workflows/publish.yml                 |  28 +-
 .github/workflows/sponsor.yml                 |  24 --
 .github/workflows/test/action.yml             |  36 --
 .github/workflows/triage.yml                  | 103 ++++++
 .netconfig                                    | 236 ++-----------
 Directory.Build.rsp                           |   2 +-
 Gemfile                                       |   2 +-
 readme.md                                     |   3 +-
 src/Directory.Build.props                     |   8 +-
 src/Directory.Build.targets                   |  26 +-
 src/ISBN/groups.js                            | 167 +++++++++-
 src/SponsorLink/Analyzer/Analyzer.csproj      |  32 --
 .../Analyzer/Properties/launchSettings.json   |  11 -
 .../Analyzer/StatusReportingAnalyzer.cs       |  26 --
 .../buildTransitive/SponsorableLib.targets    |   3 -
 src/SponsorLink/Directory.Build.props         |  43 ---
 src/SponsorLink/Directory.Build.targets       |   8 -
 src/SponsorLink/Library/Library.csproj        |  31 --
 src/SponsorLink/Library/MyClass.cs            |   5 -
 src/SponsorLink/Library/Resources.resx        | 123 -------
 src/SponsorLink/SponsorLink.targets           | 141 --------
 .../SponsorLink/AppDomainDictionary.cs        |  36 --
 .../SponsorLink/DiagnosticsManager.cs         | 138 --------
 src/SponsorLink/SponsorLink/ManifestStatus.cs |  25 --
 src/SponsorLink/SponsorLink/SponsorLink.cs    | 169 ----------
 .../SponsorLink/SponsorLink.csproj            |  46 ---
 .../SponsorLink/SponsorLink.es.resx           | 163 ---------
 src/SponsorLink/SponsorLink/SponsorLink.resx  | 164 ----------
 .../SponsorLink/SponsorLinkAnalyzer.cs        | 126 -------
 src/SponsorLink/SponsorLink/SponsorStatus.cs  |  25 --
 .../SponsorLink/SponsorableLib.targets        |  60 ----
 src/SponsorLink/SponsorLink/ThisAssembly.cs   |  31 --
 src/SponsorLink/SponsorLink/Tracing.cs        |  53 ---
 .../Devlooped.Sponsors.targets                |  99 ------
 src/SponsorLink/SponsorLink/devlooped.pub.jwk |   5 -
 src/SponsorLink/SponsorLink/sponsorable.md    |   5 -
 src/SponsorLink/SponsorLinkAnalyzer.sln       |  43 ---
 src/SponsorLink/Tests/.netconfig              |  15 -
 src/SponsorLink/Tests/Attributes.cs           |  59 ----
 src/SponsorLink/Tests/Extensions.cs           |  43 ---
 src/SponsorLink/Tests/JsonOptions.cs          |  72 ----
 src/SponsorLink/Tests/Sample.cs               |  59 ----
 src/SponsorLink/Tests/SponsorLinkTests.cs     | 126 -------
 src/SponsorLink/Tests/SponsorableManifest.cs  | 309 ------------------
 src/SponsorLink/Tests/Tests.csproj            |  42 ---
 src/SponsorLink/readme.md                     |  34 --
 52 files changed, 351 insertions(+), 2716 deletions(-)
 delete mode 100644 .github/workflows/sponsor.yml
 delete mode 100644 .github/workflows/test/action.yml
 create mode 100644 .github/workflows/triage.yml
 delete mode 100644 src/SponsorLink/Analyzer/Analyzer.csproj
 delete mode 100644 src/SponsorLink/Analyzer/Properties/launchSettings.json
 delete mode 100644 src/SponsorLink/Analyzer/StatusReportingAnalyzer.cs
 delete mode 100644 src/SponsorLink/Analyzer/buildTransitive/SponsorableLib.targets
 delete mode 100644 src/SponsorLink/Directory.Build.props
 delete mode 100644 src/SponsorLink/Directory.Build.targets
 delete mode 100644 src/SponsorLink/Library/Library.csproj
 delete mode 100644 src/SponsorLink/Library/MyClass.cs
 delete mode 100644 src/SponsorLink/Library/Resources.resx
 delete mode 100644 src/SponsorLink/SponsorLink.targets
 delete mode 100644 src/SponsorLink/SponsorLink/AppDomainDictionary.cs
 delete mode 100644 src/SponsorLink/SponsorLink/DiagnosticsManager.cs
 delete mode 100644 src/SponsorLink/SponsorLink/ManifestStatus.cs
 delete mode 100644 src/SponsorLink/SponsorLink/SponsorLink.cs
 delete mode 100644 src/SponsorLink/SponsorLink/SponsorLink.csproj
 delete mode 100644 src/SponsorLink/SponsorLink/SponsorLink.es.resx
 delete mode 100644 src/SponsorLink/SponsorLink/SponsorLink.resx
 delete mode 100644 src/SponsorLink/SponsorLink/SponsorLinkAnalyzer.cs
 delete mode 100644 src/SponsorLink/SponsorLink/SponsorStatus.cs
 delete mode 100644 src/SponsorLink/SponsorLink/SponsorableLib.targets
 delete mode 100644 src/SponsorLink/SponsorLink/ThisAssembly.cs
 delete mode 100644 src/SponsorLink/SponsorLink/Tracing.cs
 delete mode 100644 src/SponsorLink/SponsorLink/buildTransitive/Devlooped.Sponsors.targets
 delete mode 100644 src/SponsorLink/SponsorLink/devlooped.pub.jwk
 delete mode 100644 src/SponsorLink/SponsorLink/sponsorable.md
 delete mode 100644 src/SponsorLink/SponsorLinkAnalyzer.sln
 delete mode 100644 src/SponsorLink/Tests/.netconfig
 delete mode 100644 src/SponsorLink/Tests/Attributes.cs
 delete mode 100644 src/SponsorLink/Tests/Extensions.cs
 delete mode 100644 src/SponsorLink/Tests/JsonOptions.cs
 delete mode 100644 src/SponsorLink/Tests/Sample.cs
 delete mode 100644 src/SponsorLink/Tests/SponsorLinkTests.cs
 delete mode 100644 src/SponsorLink/Tests/SponsorableManifest.cs
 delete mode 100644 src/SponsorLink/Tests/Tests.csproj
 delete mode 100644 src/SponsorLink/readme.md

diff --git a/.editorconfig b/.editorconfig
index e17d14e..4cab270 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -107,6 +107,3 @@ dotnet_analyzer_diagnostic.category-Style.severity = none
 
 # VSTHRD200: Use "Async" suffix for async methods
 dotnet_diagnostic.VSTHRD200.severity = none
-
-[**/*SponsorLink*/**]
-generated_code = true
\ No newline at end of file
diff --git a/.gitattributes b/.gitattributes
index 7c37579..4f89148 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,24 +1,8 @@
-# sln, csproj files (and friends) are always CRLF, even on linux
-*.sln text eol=crlf
-*.proj text eol=crlf
-*.csproj text eol=crlf
+# normalize by default
+* text=auto encoding=UTF-8
+*.sh text eol=lf
 
 # These are windows specific files which we may as well ensure are
 # always crlf on checkout
 *.bat text eol=crlf
 *.cmd text eol=crlf
-
-# Opt in known filetypes to always normalize line endings on checkin
-# and always use native endings on checkout
-*.c text
-*.config text
-*.h text
-*.cs text
-*.md text
-*.tt text
-*.txt text
-
-# Some must always be checked out as lf so enforce that for those files
-# If these are not lf then bash/cygwin on windows will not be able to
-# excute the files
-*.sh text eol=lf
\ No newline at end of file
diff --git a/.github/release.yml b/.github/release.yml
index 9a018cd..c178589 100644
--- a/.github/release.yml
+++ b/.github/release.yml
@@ -8,7 +8,6 @@ changelog:
       - invalid
       - wontfix
       - need info
-      - docs
       - techdebt
     authors:
       - devlooped-bot
@@ -24,6 +23,7 @@ changelog:
     - title: 📝 Documentation updates
       labels: 
         - docs
+        - documentation
     - title: 🔨 Other
       labels: 
         - '*'
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index c9a0364..a109549 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -4,25 +4,31 @@
 name: build
 on: 
   workflow_dispatch:
+    inputs:
+      configuration:
+        type: choice
+        description: Configuration
+        options: 
+        - Release
+        - Debug
   push:
     branches: [ main, dev, 'dev/*', 'feature/*', 'rel/*' ]
     paths-ignore:
       - changelog.md
-      - code-of-conduct.md
-      - security.md
-      - support.md
       - readme.md
   pull_request:
     types: [opened, synchronize, reopened]
 
 env:
   DOTNET_NOLOGO: true
-  VersionPrefix: 42.42.${{ github.run_number }}
-  VersionLabel: ${{ github.ref }}
   PackOnBuild: true
   GeneratePackageOnBuild: true
+  VersionPrefix: 42.42.${{ github.run_number }}
+  VersionLabel: ${{ github.ref }}
   GH_TOKEN: ${{ secrets.GH_TOKEN }}
-  
+  MSBUILDTERMINALLOGGER: auto
+  Configuration: ${{ github.event.inputs.configuration || 'Release' }}
+
 defaults:
   run:
     shell: bash
@@ -61,14 +67,10 @@ jobs:
       - name: 🙏 build
         run: dotnet build -m:1 -bl:build.binlog
 
-      - name: ⚙ GNU grep
-        if: matrix.os == 'macOS-latest'
-        run: |
-          brew install grep
-          echo 'export PATH="/usr/local/opt/grep/libexec/gnubin:$PATH"' >> .bash_profile
-
       - name: 🧪 test
-        uses: ./.github/workflows/test
+        run: |
+          dotnet tool update -g dotnet-retest
+          dotnet retest -- --no-build
 
       - name: 🐛 logs
         uses: actions/upload-artifact@v3
@@ -77,13 +79,12 @@ jobs:
           name: logs
           path: '*.binlog'
 
-      # Only push CI package to sleet feed if building on ubuntu (fastest)
       - name: 🚀 sleet
         env:
           SLEET_CONNECTION: ${{ secrets.SLEET_CONNECTION }}
         if: env.SLEET_CONNECTION != ''
         run: |
-          dotnet tool install -g --version 4.0.18 sleet 
+          dotnet tool update sleet -g --allow-downgrade --version $(curl -s --compressed ${{ vars.SLEET_FEED_URL }} | jq '.["sleet:version"]' -r)        
           sleet push bin --config none -f --verbose -p "SLEET_FEED_CONTAINER=nuget" -p "SLEET_FEED_CONNECTIONSTRING=${{ secrets.SLEET_CONNECTION }}" -p "SLEET_FEED_TYPE=azure" || echo "No packages found"
 
   dotnet-format:
diff --git a/.github/workflows/changelog.config b/.github/workflows/changelog.config
index cd34ee7..e47bccd 100644
--- a/.github/workflows/changelog.config
+++ b/.github/workflows/changelog.config
@@ -1,7 +1,7 @@
 usernames-as-github-logins=true
 issues_wo_labels=true
 pr_wo_labels=true
-exclude-labels=bydesign,dependencies,duplicate,question,invalid,wontfix,need info,docs
+exclude-labels=bydesign,dependencies,duplicate,discussion,question,invalid,wontfix,need info,docs
 enhancement-label=:sparkles: Implemented enhancements:
 bugs-label=:bug: Fixed bugs:
 issues-label=:hammer: Other:
diff --git a/.github/workflows/includes.yml b/.github/workflows/includes.yml
index 9cdae21..15a781e 100644
--- a/.github/workflows/includes.yml
+++ b/.github/workflows/includes.yml
@@ -31,7 +31,7 @@ jobs:
       - name: ✍ pull request
         uses: peter-evans/create-pull-request@v6
         with:
-          add-paths: '**/*.md'
+          add-paths: '**.md'
           base: main
           branch: markdown-includes
           delete-branch: true
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 1c2833b..ae4240f 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -5,18 +5,20 @@
 name: publish
 on:
   release:
-    types: [released]
+    types: [prereleased, released]
 
 env:
   DOTNET_NOLOGO: true
   Configuration: Release
   PackOnBuild: true
   GeneratePackageOnBuild: true
+  VersionLabel: ${{ github.ref }}
   GH_TOKEN: ${{ secrets.GH_TOKEN }}
-  
+  MSBUILDTERMINALLOGGER: auto
+    
 jobs:
   publish:
-    runs-on: ubuntu-latest
+    runs-on: ${{ vars.PUBLISH_AGENT || 'ubuntu-latest' }}
     steps:
       - name: 🤘 checkout
         uses: actions/checkout@v4
@@ -25,10 +27,12 @@ jobs:
           fetch-depth: 0
 
       - name: 🙏 build
-        run: dotnet build -m:1 -p:version=${GITHUB_REF#refs/*/v} -bl:build.binlog
+        run: dotnet build -m:1 -bl:build.binlog
 
       - name: 🧪 test
-        uses: ./.github/workflows/test
+        run: |
+          dotnet tool update -g dotnet-retest
+          dotnet retest -- --no-build
 
       - name: 🐛 logs
         uses: actions/upload-artifact@v3
@@ -38,4 +42,16 @@ jobs:
           path: '*.binlog'
 
       - name: 🚀 nuget
-        run: dotnet nuget push ./bin/**/*.nupkg -s https://api.nuget.org/v3/index.json -k ${{secrets.NUGET_API_KEY}} --skip-duplicate
+        env:
+          NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
+        if: ${{ env.NUGET_API_KEY != '' && github.event.action != 'prereleased' }}
+        working-directory: bin
+        run: dotnet nuget push *.nupkg -s https://api.nuget.org/v3/index.json -k ${{secrets.NUGET_API_KEY}} --skip-duplicate
+
+      - name: 🚀 sleet
+        env:
+          SLEET_CONNECTION: ${{ secrets.SLEET_CONNECTION }}
+        if: env.SLEET_CONNECTION != ''
+        run: |
+          dotnet tool update sleet -g --allow-downgrade --version $(curl -s --compressed ${{ vars.SLEET_FEED_URL }} | jq '.["sleet:version"]' -r)        
+          sleet push bin --config none -f --verbose -p "SLEET_FEED_CONTAINER=nuget" -p "SLEET_FEED_CONNECTIONSTRING=${{ secrets.SLEET_CONNECTION }}" -p "SLEET_FEED_TYPE=azure" || echo "No packages found"
diff --git a/.github/workflows/sponsor.yml b/.github/workflows/sponsor.yml
deleted file mode 100644
index 1d484d3..0000000
--- a/.github/workflows/sponsor.yml
+++ /dev/null
@@ -1,24 +0,0 @@
-name: sponsor 💜
-on: 
-  issues:
-    types: [opened, edited, reopened]
-  pull_request:
-    types: [opened, edited, synchronize, reopened]
-
-jobs:
-  sponsor:
-    runs-on: ubuntu-latest
-    continue-on-error: true
-    env:
-      token: ${{ secrets.GH_TOKEN }}
-    if: ${{ !endsWith(github.event.sender.login, '[bot]') && !endsWith(github.event.sender.login, 'bot') }}      
-    steps:
-      - name: 🤘 checkout
-        if: env.token != ''
-        uses: actions/checkout@v4
-          
-      - name: 💜 sponsor 
-        if: env.token != ''
-        uses: devlooped/actions-sponsor@main
-        with:
-          token: ${{ env.token }}
diff --git a/.github/workflows/test/action.yml b/.github/workflows/test/action.yml
deleted file mode 100644
index 4a7dbae..0000000
--- a/.github/workflows/test/action.yml
+++ /dev/null
@@ -1,36 +0,0 @@
-name: test
-description: runs dotnet tests with retry
-runs:
-  using: "composite"
-  steps:
-    - name: 🧪 test
-      shell: bash --noprofile --norc {0}
-      env:
-        LC_ALL: en_US.utf8
-      run: |
-        [ -f .bash_profile ] && source .bash_profile
-        counter=0
-        exitcode=0
-        reset="\e[0m"
-        warn="\e[0;33m"
-        while [ $counter -lt 6 ]
-        do
-            # run test and forward output also to a file in addition to stdout (tee command)
-            if [ $filter ]
-            then
-                echo -e "${warn}Retry $counter for $filter ${reset}"
-                dotnet test --no-build -m:1 --blame-hang --blame-hang-timeout 5m --filter=$filter | tee ./output.log
-            else
-                dotnet test --no-build -m:1 --blame-hang --blame-hang-timeout 5m | tee ./output.log
-            fi
-            # capture dotnet test exit status, different from tee
-            exitcode=${PIPESTATUS[0]}
-            if [ $exitcode == 0 ]
-            then
-                exit 0
-            fi
-            # cat output, get failed test names, remove trailing whitespace, sort+dedupe, join as FQN~TEST with |, remove trailing |.
-            filter=$(cat ./output.log | grep -o -P '(?<=\sFailed\s)[\w\._]*' | sed 's/ *$//g' | sort -u | awk 'BEGIN { ORS="|" } { print("FullyQualifiedName~" $0) }' | grep -o -P '.*(?=\|$)')
-            ((counter++))
-        done
-        exit $exitcode
diff --git a/.github/workflows/triage.yml b/.github/workflows/triage.yml
new file mode 100644
index 0000000..56ff299
--- /dev/null
+++ b/.github/workflows/triage.yml
@@ -0,0 +1,103 @@
+name: 'triage'
+on:
+  schedule:
+    - cron: '42 0 * * *'
+
+  workflow_dispatch:
+    # Manual triggering through the GitHub UI, API, or CLI
+    inputs:
+      daysBeforeClose:
+        description: "Days before closing stale or need info issues"
+        required: true
+        default: "30"
+      daysBeforeStale:
+        description: "Days before labeling stale"
+        required: true
+        default: "180"
+      daysSinceClose:
+        description: "Days since close to lock"
+        required: true
+        default: "30"
+      daysSinceUpdate:
+        description: "Days since update to lock"
+        required: true
+        default: "30"
+
+permissions:
+  actions: write # For managing the operation state cache
+  issues: write
+  contents: read
+
+jobs:
+  stale:
+    # Do not run on forks
+    if: github.repository_owner == 'devlooped' 
+    runs-on: ubuntu-latest
+    steps:
+      - name: ⌛ rate
+        shell: pwsh
+        if: github.event_name != 'workflow_dispatch'
+        env:
+          GH_TOKEN: ${{ secrets.DEVLOOPED_TOKEN }}        
+        run: |
+          # add random sleep since we run on fixed schedule
+          $wait = get-random -max 180
+          echo "Waiting random $wait seconds to start"          
+          sleep $wait
+          # get currently authenticated user rate limit info
+          $rate = gh api rate_limit | convertfrom-json | select -expandproperty rate
+          # if we don't have at least 100 requests left, wait until reset
+          if ($rate.remaining -lt 100) {
+              $wait = ($rate.reset - (Get-Date (Get-Date).ToUniversalTime() -UFormat %s))
+              echo "Rate limit remaining is $($rate.remaining), waiting for $($wait / 1000) seconds to reset"
+              sleep $wait
+              $rate = gh api rate_limit | convertfrom-json | select -expandproperty rate
+              echo "Rate limit has reset to $($rate.remaining) requests"
+          }
+        
+      - name: ✏️ stale labeler
+        # pending merge: https://github.com/actions/stale/pull/1176
+        uses: kzu/stale@c8450312ba97b204bf37545cb249742144d6ca69
+        with:
+          ascending: true # Process the oldest issues first
+          stale-issue-label: 'stale'
+          stale-issue-message: |
+            Due to lack of recent activity, this issue has been labeled as 'stale'. 
+            It will be closed if no further activity occurs within ${{ fromJson(inputs.daysBeforeClose || 30  ) }} more days. 
+            Any new comment will remove the label.
+          close-issue-message: |
+            This issue will now be closed since it has been labeled 'stale' without activity for ${{ fromJson(inputs.daysBeforeClose || 30  ) }} days.
+          days-before-stale: ${{ fromJson(inputs.daysBeforeStale || 180) }}  
+          days-before-close: ${{ fromJson(inputs.daysBeforeClose || 30  ) }}
+          days-before-pr-close: -1 # Do not close PRs labeled as 'stale'
+          exempt-all-milestones: true
+          exempt-all-assignees: true
+          exempt-issue-labels: priority,sponsor,backed
+          exempt-authors: kzu
+
+      - name: 🤘 checkout actions
+        uses: actions/checkout@v4
+        with:
+          repository: 'microsoft/vscode-github-triage-actions'
+          ref: v42
+
+      - name: ⚙ install actions
+        run: npm install --production
+
+      - name: 🔒 issues locker
+        uses: ./locker
+        with:
+          token: ${{ secrets.DEVLOOPED_TOKEN }}
+          ignoredLabel: priority
+          daysSinceClose:  ${{ fromJson(inputs.daysSinceClose  || 30) }}
+          daysSinceUpdate: ${{ fromJson(inputs.daysSinceUpdate || 30) }}
+
+      - name: 🔒 need info closer
+        uses: ./needs-more-info-closer
+        with:
+          token: ${{ secrets.DEVLOOPED_TOKEN }}
+          label: 'need info'
+          closeDays:  ${{ fromJson(inputs.daysBeforeClose  || 30) }}
+          closeComment: "This issue has been closed automatically because it needs more information and has not had recent activity.\n\nHappy Coding!"
+          pingDays: 80
+          pingComment: "Hey @${assignee}, this issue might need further attention.\n\n@${author}, you can help us out by closing this issue if the problem no longer exists, or adding more information."
\ No newline at end of file
diff --git a/.netconfig b/.netconfig
index f84726c..17905a7 100644
--- a/.netconfig
+++ b/.netconfig
@@ -27,13 +27,13 @@
 	skip
 [file ".editorconfig"]
 	url = https://github.com/devlooped/oss/blob/main/.editorconfig
-	sha = f571a42eac3cad554810dad15139ff390db5e1db
-	etag = ba2655b8b3ce5491b1c0eea5e0af201a085c48e07542bb9ec2c928084944ea86
+	sha = e81ab754b366d52d92bd69b24bef1d5b1c610634
+	etag = 7298c6450967975a8782b5c74f3071e1910fc59686e48f9c9d5cd7c68213cf59
 	weak
 [file ".gitattributes"]
 	url = https://github.com/devlooped/oss/blob/main/.gitattributes
-	sha = 0683ee777d7d878d4bf013d7deea352685135a05
-	etag = 7acb32f5fa6d4ccd9c824605a7c2b8538497f0068c165567807d393dcf4d6bb7
+	sha = 5f92a68e302bae675b394ef343114139c075993e
+	etag = 338ba6d92c8d1774363396739c2be4257bfc58026f4b0fe92cb0ae4460e1eff7
 	weak
 [file ".github/FUNDING.yml"]
 	url = https://github.com/devlooped/.github/blob/main/.github/FUNDING.yml
@@ -47,8 +47,8 @@
 	weak
 [file ".github/workflows/build.yml"]
 	url = https://github.com/devlooped/oss/blob/main/.github/workflows/build.yml
-	sha = 5fb172362c767bef7c36478f1a6bdc264723f8f9
-	etag = 6efc7d096b25bb4bbeffe7960a1194f1ceb5d21abeda85d28b55594b648ab44a
+	sha = 5e17ad62ebb5241555a7a4d29e3ab15e5ba120d2
+	etag = f358acb1e45596bf0aad49996017da44939de30b805289c4ad205a7ccb6f99cb
 	weak
 [file ".github/workflows/changelog.yml"]
 	url = https://github.com/devlooped/oss/blob/main/.github/workflows/changelog.yml
@@ -62,8 +62,8 @@
 	weak
 [file ".github/workflows/publish.yml"]
 	url = https://github.com/devlooped/oss/blob/main/.github/workflows/publish.yml
-	sha = 5fb172362c767bef7c36478f1a6bdc264723f8f9
-	etag = 9ea4083894308a610742488923d2a44778ebba6ca73fb13424647d9a82c918b4
+	sha = 5e17ad62ebb5241555a7a4d29e3ab15e5ba120d2
+	etag = 2cc96046d8f28e7cbcde89ed56d3d89e1a70fb0de7846ee1827bee66b7dfbcf1
 	weak
 [file ".gitignore"]
 	url = https://github.com/devlooped/oss/blob/main/.gitignore
@@ -72,8 +72,8 @@
 	weak
 [file "Directory.Build.rsp"]
 	url = https://github.com/devlooped/oss/blob/main/Directory.Build.rsp
-	sha = ae25fae9d7daf0cb47d537ba870914aa3052f0c9
-	etag = 6a6c6e1d3895df953abf14c82b0899e3eea75cdcd679f6212dcfea15183d73d6
+	sha = 0f7f7f7e8a29de9b535676f75fe7c67e629a5e8c
+	etag = 0ccae83fc51f400bfd7058170bfec7aba11455e24a46a0d7e6a358da6486e255
 	weak
 [file "_config.yml"]
 	url = https://github.com/devlooped/oss/blob/main/_config.yml
@@ -92,13 +92,13 @@
 	weak
 [file "src/Directory.Build.props"]
 	url = https://github.com/devlooped/oss/blob/main/src/Directory.Build.props
-	sha = 6e96c592c7b44bfda10404b9f90e4b8fab299249
-	etag = a4925eb815bbcecc022de8d3245db069573d96ac5ecdf5f0e604f06b5577b01e
+	sha = b76de49afb376aa48eb172963ed70663b59b31d3
+	etag = c8b56f3860cc7ccb8773b7bd6189f5c7a6e3a2c27e9104c1ee201fbdc5af9873
 	weak
 [file "src/Directory.Build.targets"]
 	url = https://github.com/devlooped/oss/blob/main/src/Directory.Build.targets
-	sha = c618ea86d94402a12c7d7d10fe2b5cb8a21c3eea
-	etag = 7cb1421f00d9f6f4c00f0ca98e485dcadb927cfa6b3f0b5d4fb212525d2ce9c0
+	sha = a8b208093599263b7f2d1fe3854634c588ea5199
+	etag = 19087699f05396205e6b050d999a43b175bd242f6e8fac86f6df936310178b03
 	weak
 [file "src/kzu.snk"]
 	url = https://github.com/devlooped/oss/blob/main/src/kzu.snk
@@ -107,18 +107,13 @@
 	weak
 [file "src/ISBN/groups.js"]
 	url = https://github.com/inventaire/isbn3/blob/master/lib/groups.js
-	sha = fea7ae3df944d05c42838805b5fa71ee649a0c07
-	etag = efa16bca3e0f5bfad3686f94bd5b1e114ff60a5619ff31f103fceea7b6306e08
+	sha = 2bf73685ee06bded010fc8ca77c330dd2f9ed6e5
+	etag = ae08ae9dc02c35c2eb8e568bdda6fd0a940395d922e0ed790e0ec97cd703eae6
 	weak
 [file ".github/workflows/includes.yml"]
 	url = https://github.com/devlooped/oss/blob/main/.github/workflows/includes.yml
-	sha = 5fb172362c767bef7c36478f1a6bdc264723f8f9
-	etag = e5ee22e115c925fb85ec931cda3ac811fcc453c03904554fa3f573935b221d34
-	weak
-[file ".github/workflows/test/action.yml"]
-	url = https://github.com/devlooped/oss/blob/main/.github/workflows/test/action.yml
-	sha = 9a1b07589b9bde93bc12528e9325712a32dec418
-	etag = b54216ac431a83ce5477828d391f02046527e7f6fffd21da1d03324d352c3efb
+	sha = d152e7437fd0d6f6d9363d23cb3b78c07335ea49
+	etag = ec40db34f379d0c6d83b2ec15624f330318a172cc4f85b5417c63e86eaf601df
 	weak
 [file ".github/workflows/combine-prs.yml"]
 	url = https://github.com/devlooped/oss/blob/main/.github/workflows/combine-prs.yml
@@ -127,18 +122,13 @@
 	weak
 [file ".github/release.yml"]
 	url = https://github.com/devlooped/oss/blob/main/.github/release.yml
-	sha = 1afd173fe8f81b510c597737b0d271218e81fa73
-	etag = 482dc2c892fc7ce0cb3a01eb5d9401bee50ddfb067d8cb85873555ce63cf5438
+	sha = 0c23e24704625cf75b2cb1fdc566cef7e20af313
+	etag = 310df162242c95ed19ed12e3c96a65f77e558b46dced676ad5255eb12caafe75
 	weak
 [file ".github/workflows/changelog.config"]
 	url = https://github.com/devlooped/oss/blob/main/.github/workflows/changelog.config
-	sha = 055a8b7c94b74ae139cce919d60b83976d2a9942
-	etag = ddb17acb5872e9e69a76f9dec0ca590f25382caa2ccf750df058dcabb674db2b
-	weak
-[file ".github/workflows/sponsor.yml"]
-	url = https://github.com/devlooped/oss/blob/main/.github/workflows/sponsor.yml
-	sha = 5fb172362c767bef7c36478f1a6bdc264723f8f9
-	etag = 0849ee61af6daee29615f9632173b4e82da5bfa9d78ff28907e9408bd5acde4d
+	sha = 08d83cb510732f861416760d37702f9f55bd7f9e
+	etag = 556a28914eeeae78ca924b1105726cdaa211af365671831887aec81f5f4301b4
 	weak
 [file ".github/workflows/pages.yml"]
 	url = https://github.com/clarius/pages/blob/main/.github/workflows/pages.yml
@@ -147,181 +137,11 @@
 	weak
 [file "Gemfile"]
 	url = https://github.com/clarius/pages/blob/main/Gemfile
-	sha = 565a77f40db0863cb47ceb36f88790259a697c91
-	etag = 24e482e91192e292b633e3c17c4f095286ffb5a041d299d761b2e6ef99ee7669
-	weak
-[file "src/SponsorLink/Analyzer/Analyzer.csproj"]
-	url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Analyzer/Analyzer.csproj
-	sha = 7cda4a18313b0b38b26c0152e1007cdbb9b6ba3a
-	etag = d9444fa36daa8f4ff8f06fc2f9f600dbd8032f25ff58542d3b96676e0305677e
-	weak
-[file "src/SponsorLink/Analyzer/Properties/launchSettings.json"]
-	url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Analyzer/Properties/launchSettings.json
-	sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d
-	etag = 6c59ab4d008e3221e316c9e3b6e0da155b892680d48cdc400a39d53cb9a12aac
-	weak
-[file "src/SponsorLink/Analyzer/StatusReportingAnalyzer.cs"]
-	url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Analyzer/StatusReportingAnalyzer.cs
-	sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d
-	etag = 23d4cd16294974d85164fc26d6a7e2ae52698f23a62463db5025d69d9c166dc5
-	weak
-[file "src/SponsorLink/Analyzer/buildTransitive/SponsorableLib.targets"]
-	url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Analyzer/buildTransitive/SponsorableLib.targets
-	sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d
-	etag = 332060de0945590d7c41cd237c250b8186acd6fc2045cc85a890368c74fdf473
-	weak
-[file "src/SponsorLink/Directory.Build.props"]
-	url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Directory.Build.props
-	sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d
-	etag = 6823e1e914ecedd174276e3d53517cc0b332bb47c56402a9512cfa6aeeeb067e
-	weak
-[file "src/SponsorLink/Directory.Build.targets"]
-	url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Directory.Build.targets
-	sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d
-	etag = 9938f29c3573bf8bdb9686e1d9884dee177256b1d5dd7ee41472dd64bfbdd92d
-	weak
-[file "src/SponsorLink/Library/Library.csproj"]
-	url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Library/Library.csproj
-	sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d
-	etag = 3720f8ae0605aa64df8f6c1d9769969162175b79c93a21024653f210a42348e6
-	weak
-[file "src/SponsorLink/Library/MyClass.cs"]
-	url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Library/MyClass.cs
-	sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d
-	etag = b5b3ccd6cd14bb90dd9702b9d7e52cc22c11e601c039617738d688f9fd45d49b
-	weak
-[file "src/SponsorLink/Library/Resources.resx"]
-	url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Library/Resources.resx
-	sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d
-	etag = aff6051733d22982e761f2b414173aafeab40e0a76a142e2b33025dced213eb2
-	weak
-[file "src/SponsorLink/SponsorLink.targets"]
-	url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink.targets
-	sha = 7cda4a18313b0b38b26c0152e1007cdbb9b6ba3a
-	etag = d725bd9cfa33f35224e91748f64237e4dc66270f7e5ec7c835b78164531ae3db
-	weak
-[file "src/SponsorLink/SponsorLink/AppDomainDictionary.cs"]
-	url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/AppDomainDictionary.cs
-	sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d
-	etag = 4a70f86e73f951bca95618c221d821e38a31ef9092af4ac61447eab845671a28
-	weak
-[file "src/SponsorLink/SponsorLink/DiagnosticsManager.cs"]
-	url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/DiagnosticsManager.cs
-	sha = b2a11faac6c1c300bce8c1d45f95b585c19f2953
-	etag = 9f289f45169f35916fff1857840d4118ed134215639d6dae9016dc62004291a5
-	weak
-[file "src/SponsorLink/SponsorLink/ManifestStatus.cs"]
-	url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/ManifestStatus.cs
-	sha = b2a11faac6c1c300bce8c1d45f95b585c19f2953
-	etag = e46848f83c0436ba33a1c09a4060ad627a74db41bab66bb37ca40fce8a6532a7
-	weak
-[file "src/SponsorLink/SponsorLink/SponsorLink.cs"]
-	url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/SponsorLink.cs
-	sha = 4fca946c3201d90d30e2183f699c850dcc1bf8d5
-	etag = 96e1b1b28bfb2372bd5ffcc6bdef65ee926822b3489ce65be4e5a400884dce21
-	weak
-[file "src/SponsorLink/SponsorLink/SponsorLink.csproj"]
-	url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/SponsorLink.csproj
-	sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d
-	etag = ffaea0b580d8dccd672e749a5efd11fda318c484ca4a34428ff81524ec80ec4b
-	weak
-[file "src/SponsorLink/SponsorLink/SponsorLink.es.resx"]
-	url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/SponsorLink.es.resx
-	sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d
-	etag = ded7de7a2624b335beb462763e3580413da21e80c8b40b4c773ca46c7af4e859
-	weak
-[file "src/SponsorLink/SponsorLink/SponsorLink.resx"]
-	url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/SponsorLink.resx
-	sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d
-	etag = 7d9e89ef2cf762a6119c9c6c2ed2517b71a546838151c005400301fde8def266
-	weak
-[file "src/SponsorLink/SponsorLink/SponsorLinkAnalyzer.cs"]
-	url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/SponsorLinkAnalyzer.cs
-	sha = b2a11faac6c1c300bce8c1d45f95b585c19f2953
-	etag = fc96f7f5642cbf69b35b7e8de1756822580315f0cee61e47da3b2b1b03f68e1a
-	weak
-[file "src/SponsorLink/SponsorLink/SponsorStatus.cs"]
-	url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/SponsorStatus.cs
-	sha = 4fca946c3201d90d30e2183f699c850dcc1bf8d5
-	etag = 9a5f6f35c38c34b77796925d80addc998e204bc112fcd5fc124030060390e7c2
-	weak
-[file "src/SponsorLink/SponsorLink/SponsorableLib.targets"]
-	url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/SponsorableLib.targets
-	sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d
-	etag = 2f923a97081481a6a264d63c8ff70ce5ba65c3dbaf7ea078cbe1388fb0868e1c
-	weak
-[file "src/SponsorLink/SponsorLink/ThisAssembly.cs"]
-	url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/ThisAssembly.cs
-	sha = b2a11faac6c1c300bce8c1d45f95b585c19f2953
-	etag = 978269025f58e2bae872af25fdfd94659e234e8365e3014c18b1b20fdcd155bf
-	weak
-[file "src/SponsorLink/SponsorLink/Tracing.cs"]
-	url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/Tracing.cs
-	sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d
-	etag = 22e32872cafd080bcd5ac9084355578ef70910c8e494602ead365139dcbf40c0
-	weak
-[file "src/SponsorLink/SponsorLink/buildTransitive/Devlooped.Sponsors.targets"]
-	url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/buildTransitive/Devlooped.Sponsors.targets
-	sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d
-	etag = 72ec691a085dc34f946627f7038a82569e44f0b63a9f4a7bd60f0f7b52fd198f
-	weak
-[file "src/SponsorLink/SponsorLink/devlooped.pub.jwk"]
-	url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/devlooped.pub.jwk
-	sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d
-	etag = cf884781ff88b4d096841e3169282762a898b2050c9b5dac0013bc15bdbee267
-	weak
-[file "src/SponsorLink/SponsorLink/sponsorable.md"]
-	url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/sponsorable.md
-	sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d
-	etag = 9c275d50705a2e661f0f86f1ae5e555c0033a05e86e12f936283a5b5ef47ae77
-	weak
-[file "src/SponsorLink/SponsorLinkAnalyzer.sln"]
-	url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLinkAnalyzer.sln
-	sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d
-	etag = fc2928c9b303d81ff23891ee791a859b794d9f2d4b9f4e81b9ed15e5b74db487
-	weak
-[file "src/SponsorLink/Tests/.netconfig"]
-	url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Tests/.netconfig
-	sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d
-	etag = 089a26cdb722d57014c8b8104cc6f4e770868efdc49ae3119eebc873f00a316e
-	weak
-[file "src/SponsorLink/Tests/Attributes.cs"]
-	url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Tests/Attributes.cs
-	sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d
-	etag = 1d7c17a2c9424db73746112c338a39e0000134ac878b398e2aa88f7ea5c0c488
-	weak
-[file "src/SponsorLink/Tests/Extensions.cs"]
-	url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Tests/Extensions.cs
-	sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d
-	etag = f68e11894103f8748ce290c29927bf1e4f749e743ae33d5350e72ed22c15d245
-	weak
-[file "src/SponsorLink/Tests/JsonOptions.cs"]
-	url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Tests/JsonOptions.cs
-	sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d
-	etag = 6e9a1b12757a97491441b9534ced4e5dac6d9d6334008fa0cd20575650bbd935
-	weak
-[file "src/SponsorLink/Tests/Sample.cs"]
-	url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Tests/Sample.cs
-	sha = e732f6a2c44a2f7940a1868a92cd66523f74ed24
-	etag = db968d1d665b77a17e13bc7ca3d43ea65ed05cbebc18669f1b607ebe0e38a59a
-	weak
-[file "src/SponsorLink/Tests/SponsorLinkTests.cs"]
-	url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Tests/SponsorLinkTests.cs
-	sha = d74f5111504a0fae6e5a1e68ca92bf7afddb3254
-	etag = 1fa41250bd984e8aa840a966d34ce0e94f2111d1422d7f50b864c38364fcf4a4
-	weak
-[file "src/SponsorLink/Tests/SponsorableManifest.cs"]
-	url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Tests/SponsorableManifest.cs
-	sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d
-	etag = e0c95e7fc6c0499dbc8c5cd28aa9a6a5a49c9d0ad41fe028a5a085aca7e00eaf
-	weak
-[file "src/SponsorLink/Tests/Tests.csproj"]
-	url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Tests/Tests.csproj
-	sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d
-	etag = 237409e155202ec1b845593195d30057a949b2b18ae46a575e4cf480e4e2c8fe
+	sha = 90fa16ed0e7300a78a38ee1d23c34a7e875aab27
+	etag = 3dd7febc8ae6760f19abfe787711f469c288cd803a6f1c545edec34264d48e71
 	weak
-[file "src/SponsorLink/readme.md"]
-	url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/readme.md
-	sha = 827a1d18bf0245978d81bcd3d52e9e6f1584d1ef
-	etag = 079b4aedba2aa9851e609b569f25c55db8d5922e3dbb1adc22611ce4d6cfe465
+[file ".github/workflows/triage.yml"]
+	url = https://github.com/devlooped/oss/blob/main/.github/workflows/triage.yml
+	sha = 33000c0c4ab4eb4e0e142fa54515b811a189d55c
+	etag = 013a47739e348f06891f37c45164478cca149854e6cd5c5158e6f073f852b61a
 	weak
diff --git a/Directory.Build.rsp b/Directory.Build.rsp
index 7c0dbc1..509cc66 100644
--- a/Directory.Build.rsp
+++ b/Directory.Build.rsp
@@ -2,4 +2,4 @@
 -nr:false
 -m:1
 -v:m
--clp:Summary;ForceNoAlign
\ No newline at end of file
+-clp:Summary;ForceNoAlign
diff --git a/Gemfile b/Gemfile
index ed99566..fd95539 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,3 +1,3 @@
 source 'https://rubygems.org'
 
-gem 'github-pages', '~> 209', group: :jekyll_plugins
+gem 'github-pages', '~> 231', group: :jekyll_plugins
diff --git a/readme.md b/readme.md
index 7ed0f35..a5e5f59 100644
--- a/readme.md
+++ b/readme.md
@@ -37,7 +37,6 @@ isbn3 repository which in turn fetches [isbn-international.org](https://www.isbn
 [![Clarius Org](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/clarius.png "Clarius Org")](https://github.com/clarius)
 [![Kirill Osenkov](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/KirillOsenkov.png "Kirill Osenkov")](https://github.com/KirillOsenkov)
 [![MFB Technologies, Inc.](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/MFB-Technologies-Inc.png "MFB Technologies, Inc.")](https://github.com/MFB-Technologies-Inc)
-[![Stephen Shaw](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/decriptor.png "Stephen Shaw")](https://github.com/decriptor)
 [![Torutek](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/torutek-gh.png "Torutek")](https://github.com/torutek-gh)
 [![DRIVE.NET, Inc.](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/drivenet.png "DRIVE.NET, Inc.")](https://github.com/drivenet)
 [![Ashley Medway](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/AshleyMedway.png "Ashley Medway")](https://github.com/AshleyMedway)
@@ -55,7 +54,6 @@ isbn3 repository which in turn fetches [isbn-international.org](https://www.isbn
 [![Ix Technologies B.V.](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/IxTechnologies.png "Ix Technologies B.V.")](https://github.com/IxTechnologies)
 [![David JENNI](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/davidjenni.png "David JENNI")](https://github.com/davidjenni)
 [![Jonathan ](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/Jonathan-Hickey.png "Jonathan ")](https://github.com/Jonathan-Hickey)
-[![Oleg Kyrylchuk](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/okyrylchuk.png "Oleg Kyrylchuk")](https://github.com/okyrylchuk)
 [![Charley Wu](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/akunzai.png "Charley Wu")](https://github.com/akunzai)
 [![Jakob Tikjøb Andersen](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/jakobt.png "Jakob Tikjøb Andersen")](https://github.com/jakobt)
 [![Seann Alexander](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/seanalexander.png "Seann Alexander")](https://github.com/seanalexander)
@@ -69,6 +67,7 @@ isbn3 repository which in turn fetches [isbn-international.org](https://www.isbn
 [![Vezel](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/vezel-dev.png "Vezel")](https://github.com/vezel-dev)
 [![ChilliCream](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/ChilliCream.png "ChilliCream")](https://github.com/ChilliCream)
 [![4OTC](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/4OTC.png "4OTC")](https://github.com/4OTC)
+[![Vincent Limo](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/v-limo.png "Vincent Limo")](https://github.com/v-limo)
 
 
 <!-- sponsors.md -->
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index 50fc169..381c383 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -46,8 +46,6 @@
 
   <PropertyGroup Label="Build">
     <Configuration Condition="'$(Configuration)' == '' and $(CI)">Release</Configuration>
-    <GenerateDocumentationFile>true</GenerateDocumentationFile>
-    <GenerateDocumentationFile Condition="$(MSBuildProjectName.Contains('Tests'))">false</GenerateDocumentationFile>
     <LangVersion>Latest</LangVersion>
 
     <!-- See https://docs.microsoft.com/en-us/dotnet/standard/assembly/reference-assemblies -->
@@ -118,6 +116,8 @@
 
   <PropertyGroup Label="Version" Condition="$(VersionLabel) != ''">
     <_VersionLabel>$(VersionLabel.Replace('refs/heads/', ''))</_VersionLabel>
+    <_VersionLabel>$(_VersionLabel.Replace('refs/tags/v', ''))</_VersionLabel>
+
     <!-- For PRs, we just need a fixed package version numbered after the PR # itself, so remove the commits # at the end -->
     <_VersionLabel Condition="$(_VersionLabel.Contains('refs/pull/'))">$(VersionLabel.TrimEnd('.0123456789'))</_VersionLabel>
     <!-- Next replace the prefix for simply 'pr', so we end up with 'pr99/merge' by default -->
@@ -128,7 +128,9 @@
     <_VersionLabel>$(_VersionLabel.Replace('/', '-'))</_VersionLabel>
 
     <!-- Set sanitized version to the actual version suffix used in build/pack -->
-    <VersionSuffix>$(_VersionLabel)</VersionSuffix>
+    <VersionSuffix Condition="!$(VersionLabel.Contains('refs/tags/'))">$(_VersionLabel)</VersionSuffix>
+    <!-- Special case for tags, the label is actually the version. Backs compat since passed-in value overrides MSBuild-set one -->
+    <Version Condition="$(VersionLabel.Contains('refs/tags/'))">$(_VersionLabel)</Version>
   </PropertyGroup>
 
   <ItemGroup Label="ThisAssembly.Project">
diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets
index 0cb1e4e..6232750 100644
--- a/src/Directory.Build.targets
+++ b/src/Directory.Build.targets
@@ -4,6 +4,13 @@
   <PropertyGroup Condition="'$(CI)' == 'true' and '$(Language)' == 'C#'">
     <DefineConstants>CI;$(DefineConstants)</DefineConstants>
   </PropertyGroup>
+
+  <PropertyGroup Label="Build">
+    <!-- Tests projects don't need API docs, typically -->
+    <GenerateDocumentationFile Condition="$(GenerateDocumentationFile) == '' and $(IsTestProject) == 'true'">false</GenerateDocumentationFile>
+    <GenerateDocumentationFile Condition="$(GenerateDocumentationFile) == '' and $(MSBuildProjectName.Contains('Tests'))">false</GenerateDocumentationFile>
+    <GenerateDocumentationFile Condition="$(GenerateDocumentationFile) == ''">true</GenerateDocumentationFile>
+  </PropertyGroup>
   
   <PropertyGroup Condition="'$(IsPackable)' == ''">
     <IsPackable Condition="'$(PackAsTool)' == 'true'">true</IsPackable>
@@ -27,23 +34,28 @@
   
   <ItemGroup Condition="'$(IsPackable)' == 'true'" Label="NuGet">
     <!-- This is compatible with nugetizer and SDK pack -->
+    <!-- Only difference is we don't copy either to output directory -->
 
     <!-- Project-level icon/readme will already be part of None items -->
     <None Update="@(None -> WithMetadataValue('Filename', 'icon'))" 
           Pack="true" PackagePath="%(Filename)%(Extension)" 
+          CopyToOutputDirectory="Never"
           Condition="'$(PackageIcon)' != ''" />
 
     <None Update="@(None -> WithMetadataValue('Filename', 'readme'))" 
           Pack="true" PackagePath="%(Filename)%(Extension)" 
+          CopyToOutputDirectory="Never"
           Condition="'$(PackReadme)' != 'false' and '$(PackageReadmeFile)' != ''" />
     
     <!-- src-level will need explicit inclusion -->
     <None Include="$(MSBuildThisFileDirectory)icon.png" Link="icon.png" Visible="false" 
           Pack="true" PackagePath="%(Filename)%(Extension)"
+          CopyToOutputDirectory="Never"
           Condition="Exists('$(MSBuildThisFileDirectory)icon.png') and !Exists('$(MSBuildProjectDirectory)\icon.png')" />
 
     <None Include="$(MSBuildThisFileDirectory)readme.md" Link="readme.md"  
           Pack="true" PackagePath="%(Filename)%(Extension)"
+          CopyToOutputDirectory="Never"
           Condition="'$(PackReadme)' != 'false' and Exists('$(MSBuildThisFileDirectory)readme.md') and !Exists('$(MSBuildProjectDirectory)\readme.md')" />
   </ItemGroup>
 
@@ -94,19 +106,17 @@
     <RepositoryBranch Condition="'$(RepositoryBranch)' == '' and '$(BUDDY_EXECUTION_BRANCH)' != ''">$(BUDDY_EXECUTION_BRANCH)</RepositoryBranch>
   </PropertyGroup>  
 
-  <PropertyGroup Condition="'$(EnableRexCodeGenerator)' == 'true'">
-    <!-- VSCode/Razor compatibility -->
-    <CoreCompileDependsOn>PrepareResources;$(CoreCompileDependsOn)</CoreCompileDependsOn>
+  <PropertyGroup>
+    <!-- Default to Just Works resources generation. See https://www.cazzulino.com/resources.html -->
+    <CoreCompileDependsOn>CoreResGen;$(CoreCompileDependsOn)</CoreCompileDependsOn>
   </PropertyGroup>
-  
+
   <ItemGroup>
     <!-- Consider the project out of date if any of these files changes -->
     <UpToDateCheck Include="@(None);@(Content);@(EmbeddedResource)" />
-    <!-- We'll typically use ThisAssembly.Strings instead of the built-in resource manager codegen -->
-    <EmbeddedResource Update="@(EmbeddedResource)"  Generator="" Condition="'$(EnableRexCodeGenerator)' != 'true'" />
-    <EmbeddedResource Update="@(EmbeddedResource)" Condition="'$(EnableRexCodeGenerator)' == 'true'">
+    <!-- Opt-in to typed resource generation by setting custom tool to MSBuild:Compile -->
+    <EmbeddedResource Update="@(EmbeddedResource -> WithMetadataValue('Generator', 'MSBuild:Compile'))" Type="Resx">
       <!-- Default to Just Works resources generation. See https://www.cazzulino.com/resources.html -->
-      <Generator>MSBuild:Compile</Generator>
       <StronglyTypedFileName>$(IntermediateOutputPath)\$([MSBuild]::ValueOrDefault('%(RelativeDir)', '').Replace('\', '.').Replace('/', '.'))%(Filename).g$(DefaultLanguageSourceExtension)</StronglyTypedFileName>
       <StronglyTypedLanguage>$(Language)</StronglyTypedLanguage>
       <StronglyTypedNamespace Condition="'%(RelativeDir)' == ''">$(RootNamespace)</StronglyTypedNamespace>
diff --git a/src/ISBN/groups.js b/src/ISBN/groups.js
index 7760fc5..a8e6e65 100644
--- a/src/ISBN/groups.js
+++ b/src/ISBN/groups.js
@@ -850,6 +850,18 @@ module.exports = {
     ranges: [
       [
         '00',
+        '25'
+      ],
+      [
+        '2600',
+        '2649'
+      ],
+      [
+        '26500',
+        '26999'
+      ],
+      [
+        '27',
         '39'
       ],
       [
@@ -1160,7 +1172,7 @@ module.exports = {
       ],
       [
         '110',
-        '519'
+        '524'
       ],
       [
         '5250',
@@ -1319,11 +1331,15 @@ module.exports = {
     ranges: [
       [
         '300',
-        '349'
+        '399'
       ],
       [
         '6500',
         '6849'
+      ],
+      [
+        '95000',
+        '99999'
       ]
     ]
   },
@@ -2384,6 +2400,22 @@ module.exports = {
         '64771',
         '64771'
       ],
+      [
+        '647723',
+        '647729'
+      ],
+      [
+        '64773',
+        '64773'
+      ],
+      [
+        '647740',
+        '647769'
+      ],
+      [
+        '64777',
+        '64779'
+      ],
       [
         '647800',
         '647809'
@@ -2410,6 +2442,66 @@ module.exports = {
       ],
       [
         '6479',
+        '6493'
+      ],
+      [
+        '649400',
+        '649409'
+      ],
+      [
+        '64941',
+        '64942'
+      ],
+      [
+        '649430',
+        '649449'
+      ],
+      [
+        '64945',
+        '64946'
+      ],
+      [
+        '649470',
+        '649479'
+      ],
+      [
+        '64948',
+        '64948'
+      ],
+      [
+        '649490',
+        '649499'
+      ],
+      [
+        '6495',
+        '6497'
+      ],
+      [
+        '64980',
+        '64980'
+      ],
+      [
+        '649810',
+        '649829'
+      ],
+      [
+        '64983',
+        '64984'
+      ],
+      [
+        '649850',
+        '649869'
+      ],
+      [
+        '64987',
+        '64987'
+      ],
+      [
+        '649880',
+        '649883'
+      ],
+      [
+        '6499',
         '8999'
       ],
       [
@@ -3406,7 +3498,11 @@ module.exports = {
       ],
       [
         '90',
-        '96'
+        '95'
+      ],
+      [
+        '9600',
+        '9699'
       ],
       [
         '970',
@@ -3526,14 +3622,14 @@ module.exports = {
       ],
       [
         '4000',
-        '9399'
+        '5999'
       ],
       [
         '94',
         '94'
       ],
       [
-        '97',
+        '96',
         '99'
       ]
     ]
@@ -3818,19 +3914,36 @@ module.exports = {
       ]
     ]
   },
+  '978-9909': {
+    name: 'Tunisia',
+    ranges: [
+      [
+        '00',
+        '19'
+      ],
+      [
+        '750',
+        '849'
+      ],
+      [
+        '9800',
+        '9999'
+      ]
+    ]
+  },
   '978-9910': {
     name: 'Uzbekistan',
     ranges: [
       [
         '01',
-        '08'
+        '09'
       ],
       [
         '650',
         '799'
       ],
       [
-        '9000',
+        '8800',
         '9999'
       ]
     ]
@@ -3937,14 +4050,22 @@ module.exports = {
       ],
       [
         '600',
-        '799'
+        '789'
       ],
       [
-        '80',
+        '79',
         '91'
       ],
       [
-        '9250',
+        '9200',
+        '9399'
+      ],
+      [
+        '94',
+        '94'
+      ],
+      [
+        '9500',
         '9999'
       ]
     ]
@@ -7070,7 +7191,7 @@ module.exports = {
         '03'
       ],
       [
-        '075',
+        '050',
         '099'
       ],
       [
@@ -7179,7 +7300,7 @@ module.exports = {
         '0'
       ],
       [
-        '140',
+        '120',
         '149'
       ],
       [
@@ -7209,10 +7330,10 @@ module.exports = {
       ],
       [
         '50',
-        '68'
+        '71'
       ],
       [
-        '900',
+        '885',
         '999'
       ]
     ]
@@ -7337,10 +7458,10 @@ module.exports = {
       ],
       [
         '50',
-        '54'
+        '57'
       ],
       [
-        '975',
+        '960',
         '999'
       ]
     ]
@@ -7367,7 +7488,7 @@ module.exports = {
     ranges: [
       [
         '0',
-        '1'
+        '2'
       ],
       [
         '50',
@@ -7512,6 +7633,14 @@ module.exports = {
         '200',
         '229'
       ],
+      [
+        '230',
+        '239'
+      ],
+      [
+        '3000',
+        '3199'
+      ],
       [
         '3200',
         '3499'
@@ -7535,6 +7664,10 @@ module.exports = {
       [
         '9900000',
         '9929999'
+      ],
+      [
+        '9985000',
+        '9999999'
       ]
     ]
   }
diff --git a/src/SponsorLink/Analyzer/Analyzer.csproj b/src/SponsorLink/Analyzer/Analyzer.csproj
deleted file mode 100644
index 963c77b..0000000
--- a/src/SponsorLink/Analyzer/Analyzer.csproj
+++ /dev/null
@@ -1,32 +0,0 @@
-<Project Sdk="Microsoft.NET.Sdk">
-
-  <PropertyGroup>
-    <TargetFramework>netstandard2.0</TargetFramework>
-    <IsRoslynComponent>true</IsRoslynComponent>
-    <PackFolder>analyzers/dotnet/roslyn4.0</PackFolder>
-    <PublishRepositoryUrl>true</PublishRepositoryUrl>
-    <CustomAfterMicrosoftCSharpTargets>$(MSBuildThisFileDirectory)..\SponsorLink.targets</CustomAfterMicrosoftCSharpTargets>
-    <MergeAnalyzerAssemblies>true</MergeAnalyzerAssemblies>
-    <ImplicitUsings>disable</ImplicitUsings>
-  </PropertyGroup>
-
-  <ItemGroup>
-    <PackageReference Include="NuGetizer" Version="1.2.2" />
-    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.1" Pack="false" />
-    <PackageReference Include="PolySharp" Version="1.14.1" PrivateAssets="all" />
-    <PackageReference Include="ThisAssembly.AssemblyInfo" Version="1.4.3" PrivateAssets="all" />
-    <PackageReference Include="ThisAssembly.Git" Version="1.4.3" PrivateAssets="all" />
-    <PackageReference Include="ThisAssembly.Constants" Version="1.4.3" PrivateAssets="all" />
-    <PackageReference Include="ThisAssembly.Strings" Version="1.4.3" PrivateAssets="all" />
-    <PackageReference Include="ThisAssembly.Project" Version="1.4.3" PrivateAssets="all" />
-  </ItemGroup>
-
-  <ItemGroup>
-    <InternalsVisibleTo Include="Tests" />
-  </ItemGroup>
-
-  <ItemGroup>
-    <None Update="buildTransitive\SponsorableLib.targets" Pack="true" />
-  </ItemGroup>
-
-</Project>
\ No newline at end of file
diff --git a/src/SponsorLink/Analyzer/Properties/launchSettings.json b/src/SponsorLink/Analyzer/Properties/launchSettings.json
deleted file mode 100644
index de45107..0000000
--- a/src/SponsorLink/Analyzer/Properties/launchSettings.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
-  "profiles": {
-    "SponsorableLib": {
-      "commandName": "DebugRoslynComponent",
-      "targetProject": "..\\Tests\\Tests.csproj",
-      "environmentVariables": {
-        "SPONSORLINK_TRACE": "true"
-      }
-    }
-  }
-}
\ No newline at end of file
diff --git a/src/SponsorLink/Analyzer/StatusReportingAnalyzer.cs b/src/SponsorLink/Analyzer/StatusReportingAnalyzer.cs
deleted file mode 100644
index e21acb7..0000000
--- a/src/SponsorLink/Analyzer/StatusReportingAnalyzer.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using System.Collections.Immutable;
-using Devlooped.Sponsors;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.Diagnostics;
-using static Devlooped.Sponsors.SponsorLink;
-using static ThisAssembly.Constants;
-
-namespace Analyzer;
-
-[DiagnosticAnalyzer(LanguageNames.CSharp)]
-public class StatusReportingAnalyzer : DiagnosticAnalyzer
-{
-    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray<DiagnosticDescriptor>.Empty;
-
-    public override void Initialize(AnalysisContext context)
-    {
-        context.EnableConcurrentExecution();
-        context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
-
-        context.RegisterCodeBlockAction(c =>
-        {
-            var status = Diagnostics.GetStatus(Funding.Product);
-            Tracing.Trace($"Status: {status}");
-        });
-    }
-}
\ No newline at end of file
diff --git a/src/SponsorLink/Analyzer/buildTransitive/SponsorableLib.targets b/src/SponsorLink/Analyzer/buildTransitive/SponsorableLib.targets
deleted file mode 100644
index fd1e6e4..0000000
--- a/src/SponsorLink/Analyzer/buildTransitive/SponsorableLib.targets
+++ /dev/null
@@ -1,3 +0,0 @@
-<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <Import Project="Devlooped.Sponsors.targets"/>
-</Project>
\ No newline at end of file
diff --git a/src/SponsorLink/Directory.Build.props b/src/SponsorLink/Directory.Build.props
deleted file mode 100644
index c0a3e42..0000000
--- a/src/SponsorLink/Directory.Build.props
+++ /dev/null
@@ -1,43 +0,0 @@
-<Project>
-
-  <PropertyGroup>
-    <GenerateDocumentationFile>false</GenerateDocumentationFile>
-    <LangVersion>latest</LangVersion>
-    <ImplicitUsings>true</ImplicitUsings>
-    <Nullable>annotations</Nullable>
-    <PackOnBuild>true</PackOnBuild>
-    <!-- Avoid deleting older packed versions to avoid rebuild errors (since package is in same solution, which is uncommon) -->
-    <EnablePackCleanup>false</EnablePackCleanup>
-    <PackageOutputPath>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)bin'))</PackageOutputPath>
-
-    <RestoreSources>https://pkg.kzu.app/index.json;https://api.nuget.org/v3/index.json</RestoreSources>
-    <RestoreSources Condition="Exists('$(PackageOutputPath)')">$(PackageOutputPath);$(RestoreSources)</RestoreSources>
-
-    <!-- Roslyn caches analyzers aggressively so we are better off using a very dynamic version number 
-         for local builds where a quick devloop is key. We bump version every 10 seconds -->
-    <Version>42.42.$([System.Math]::Floor($([MSBuild]::Divide($([System.DateTime]::Now.TimeOfDay.TotalSeconds), 10))))</Version>
-
-    <Product>SponsorableLib</Product>
-  </PropertyGroup>
-
-  <ItemGroup>
-    <!--<Constant Include="Funding.Product" Value="$(Product)" />
-    <Constant Include="Funding.AnalyzerPrefix" Value="SLIB" />-->
-    <!--<Constant Include="Funding.GraceDays" Value="21" />-->
-  </ItemGroup>
-
-  <!-- DOGFOODING LOCAL BUILDS -->
-  <!-- Create a Directory.targets.user alongside this file, with the following content 
-       (update the version number to the number of the built local package): -->
-  <!-- 
-  <Project>
-
-    <ItemGroup Condition="Exists('$(DevPath)')">
-      <PackageReference Update="@(PackageReference -> WithMetadataValue('Identity', 'Devlooped.SponsorLink'))" 
-                        Version="42.42.6587" />
-    </ItemGroup>
-
-  </Project>
-  -->
-
-</Project>
diff --git a/src/SponsorLink/Directory.Build.targets b/src/SponsorLink/Directory.Build.targets
deleted file mode 100644
index 4ce4c80..0000000
--- a/src/SponsorLink/Directory.Build.targets
+++ /dev/null
@@ -1,8 +0,0 @@
-<Project>
-
-  <ItemGroup Condition="Exists('$(PackageOutputPath)\SponsorableLib.$(Version).nupkg')">
-    <PackageReference Update="@(PackageReference -> WithMetadataValue('Identity', 'SponsorableLib'))"
-                      Version="$(Version)" />
-  </ItemGroup>
-
-</Project>
\ No newline at end of file
diff --git a/src/SponsorLink/Library/Library.csproj b/src/SponsorLink/Library/Library.csproj
deleted file mode 100644
index f351273..0000000
--- a/src/SponsorLink/Library/Library.csproj
+++ /dev/null
@@ -1,31 +0,0 @@
-<Project Sdk="Microsoft.NET.Sdk">
-
-  <PropertyGroup>
-    <TargetFramework>netstandard2.0</TargetFramework>
-    <PackNone>true</PackNone>
-    <PackageId>SponsorableLib</PackageId>
-    <Description>Sample library incorporating SponsorLink checks</Description>
-    <PackOnBuild>true</PackOnBuild>
-  </PropertyGroup>
-
-  <ItemGroup>
-    <PackageReference Include="NuGetizer" Version="1.2.2" />
-  </ItemGroup>
-
-  <ItemGroup>
-    <ProjectReference Include="..\Analyzer\Analyzer.csproj" ReferenceOutputAssembly="false" OutputType="Analyzer" />
-  </ItemGroup>
-
-  <ItemGroup>
-    <EmbeddedResource Update="Resources.resx">
-      <!-- Default to Just Works resources generation. See https://www.cazzulino.com/resources.html -->
-      <Generator>MSBuild:Compile</Generator>
-      <StronglyTypedFileName>$(IntermediateOutputPath)\$([MSBuild]::ValueOrDefault('%(RelativeDir)', '').Replace('\', '.').Replace('/', '.'))%(Filename).g$(DefaultLanguageSourceExtension)</StronglyTypedFileName>
-      <StronglyTypedLanguage>$(Language)</StronglyTypedLanguage>
-      <StronglyTypedNamespace Condition="'%(RelativeDir)' == ''">$(RootNamespace)</StronglyTypedNamespace>
-      <StronglyTypedNamespace Condition="'%(RelativeDir)' != ''">$(RootNamespace).$([MSBuild]::ValueOrDefault('%(RelativeDir)', '').Replace('\', '.').Replace('/', '.').TrimEnd('.'))</StronglyTypedNamespace>
-      <StronglyTypedClassName>%(Filename)</StronglyTypedClassName>
-    </EmbeddedResource>
-  </ItemGroup>
-
-</Project>
diff --git a/src/SponsorLink/Library/MyClass.cs b/src/SponsorLink/Library/MyClass.cs
deleted file mode 100644
index 7b7f6f5..0000000
--- a/src/SponsorLink/Library/MyClass.cs
+++ /dev/null
@@ -1,5 +0,0 @@
-namespace SponsorableLib;
-
-public class MyClass
-{
-}
diff --git a/src/SponsorLink/Library/Resources.resx b/src/SponsorLink/Library/Resources.resx
deleted file mode 100644
index 636fedc..0000000
--- a/src/SponsorLink/Library/Resources.resx
+++ /dev/null
@@ -1,123 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<root>
-  <!-- 
-    Microsoft ResX Schema 
-    
-    Version 2.0
-    
-    The primary goals of this format is to allow a simple XML format 
-    that is mostly human readable. The generation and parsing of the 
-    various data types are done through the TypeConverter classes 
-    associated with the data types.
-    
-    Example:
-    
-    ... ado.net/XML headers & schema ...
-    <resheader name="resmimetype">text/microsoft-resx</resheader>
-    <resheader name="version">2.0</resheader>
-    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
-    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
-    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
-    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
-    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
-        <value>[base64 mime encoded serialized .NET Framework object]</value>
-    </data>
-    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
-        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
-        <comment>This is a comment</comment>
-    </data>
-                
-    There are any number of "resheader" rows that contain simple 
-    name/value pairs.
-    
-    Each data row contains a name, and value. The row also contains a 
-    type or mimetype. Type corresponds to a .NET class that support 
-    text/value conversion through the TypeConverter architecture. 
-    Classes that don't support this are serialized and stored with the 
-    mimetype set.
-    
-    The mimetype is used for serialized objects, and tells the 
-    ResXResourceReader how to depersist the object. This is currently not 
-    extensible. For a given mimetype the value must be set accordingly:
-    
-    Note - application/x-microsoft.net.object.binary.base64 is the format 
-    that the ResXResourceWriter will generate, however the reader can 
-    read any of the formats listed below.
-    
-    mimetype: application/x-microsoft.net.object.binary.base64
-    value   : The object must be serialized with 
-            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
-            : and then encoded with base64 encoding.
-    
-    mimetype: application/x-microsoft.net.object.soap.base64
-    value   : The object must be serialized with 
-            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
-            : and then encoded with base64 encoding.
-
-    mimetype: application/x-microsoft.net.object.bytearray.base64
-    value   : The object must be serialized into a byte array 
-            : using a System.ComponentModel.TypeConverter
-            : and then encoded with base64 encoding.
-    -->
-  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
-    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
-    <xsd:element name="root" msdata:IsDataSet="true">
-      <xsd:complexType>
-        <xsd:choice maxOccurs="unbounded">
-          <xsd:element name="metadata">
-            <xsd:complexType>
-              <xsd:sequence>
-                <xsd:element name="value" type="xsd:string" minOccurs="0" />
-              </xsd:sequence>
-              <xsd:attribute name="name" use="required" type="xsd:string" />
-              <xsd:attribute name="type" type="xsd:string" />
-              <xsd:attribute name="mimetype" type="xsd:string" />
-              <xsd:attribute ref="xml:space" />
-            </xsd:complexType>
-          </xsd:element>
-          <xsd:element name="assembly">
-            <xsd:complexType>
-              <xsd:attribute name="alias" type="xsd:string" />
-              <xsd:attribute name="name" type="xsd:string" />
-            </xsd:complexType>
-          </xsd:element>
-          <xsd:element name="data">
-            <xsd:complexType>
-              <xsd:sequence>
-                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
-                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
-              </xsd:sequence>
-              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
-              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
-              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
-              <xsd:attribute ref="xml:space" />
-            </xsd:complexType>
-          </xsd:element>
-          <xsd:element name="resheader">
-            <xsd:complexType>
-              <xsd:sequence>
-                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
-              </xsd:sequence>
-              <xsd:attribute name="name" type="xsd:string" use="required" />
-            </xsd:complexType>
-          </xsd:element>
-        </xsd:choice>
-      </xsd:complexType>
-    </xsd:element>
-  </xsd:schema>
-  <resheader name="resmimetype">
-    <value>text/microsoft-resx</value>
-  </resheader>
-  <resheader name="version">
-    <value>2.0</value>
-  </resheader>
-  <resheader name="reader">
-    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </resheader>
-  <resheader name="writer">
-    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </resheader>
-  <data name="Foo" xml:space="preserve">
-    <value>Bar</value>
-  </data>
-</root>
\ No newline at end of file
diff --git a/src/SponsorLink/SponsorLink.targets b/src/SponsorLink/SponsorLink.targets
deleted file mode 100644
index de93845..0000000
--- a/src/SponsorLink/SponsorLink.targets
+++ /dev/null
@@ -1,141 +0,0 @@
-<Project>
-  <!-- For inclusion in analyzer projects that want to integrate SponsorLink -->
-  <!-- Import using the following property: -->
-  <!--<CustomAfterMicrosoftCSharpTargets>$(MSBuildThisFileDirectory)..\SponsorLink\SponsorLink.targets</CustomAfterMicrosoftCSharpTargets>-->
-
-  <PropertyGroup>
-    <ShowSponsorLinkInProject Condition="$(ShowSponsorLinkInProject) == '' and '$(TargetFramework)' == 'netstandard2.0'">true</ShowSponsorLinkInProject>
-    <!-- This ensures we expose only the main assembly in the Dependencies > Analyzers node -->
-    <MergeAnalyzerAssemblies Condition="'$(MergeAnalyzerAssemblies)' == '' and '$(Configuration)' == 'Release'">true</MergeAnalyzerAssemblies>
-    <!-- If we are going to merge files, we need to copy local -->
-    <CopyLocalLockFileAssemblies Condition="'$(MergeAnalyzerAssemblies)' == 'true'">true</CopyLocalLockFileAssemblies>
-
-    <!-- Read public key we validate manifests against -->
-    <DevloopedJwk>$([System.IO.File]::ReadAllText('$(MSBuildThisFileDirectory)SponsorLink/devlooped.pub.jwk'))</DevloopedJwk>
-
-    <!-- Default funding product the Product, which already part of ThisAssembly -->
-    <FundingProduct Condition="'$(FundingProduct)' == ''">$(Product)</FundingProduct>
-    <!-- Default prefix is the joined upper-case letters in the product name (i.e. for ThisAssembly, TA) -->
-    <FundingPrefix Condition="'$(FundingPrefix)' == ''">$([System.Text.RegularExpressions.Regex]::Replace("$(FundingProduct)", "[^A-Z]", ""))</FundingPrefix>
-    <!-- Default grace days for an expired sponsor manifest -->
-    <FundingGrace Condition="'$(FundingGrace)' == ''">21</FundingGrace>
-  </PropertyGroup>
-
-  <ItemGroup>
-    <AssemblyMetadata Include="Funding.GitHub.devlooped" Value="$(DevloopedJwk)" />
-    <Constant Include="Funding.Product" Value="$(FundingProduct)" />
-    <Constant Include="Funding.Prefix" Value="$(FundingPrefix)" />
-    <Constant Include="Funding.Grace" Value="$(FundingGrace)" />
-  </ItemGroup>
-
-  <ItemGroup>
-    <Compile Include="$(MSBuildThisFileDirectory)SponsorLink/*.cs"
-             Exclude="$(MSBuildThisFileDirectory)SponsorLink/bin/**;$(MSBuildThisFileDirectory)SponsorLink/obj/**"
-             Source="SponsorLink"/>
-    <EmbeddedResource Include="$(MSBuildThisFileDirectory)SponsorLink/*.resx"
-                      Exclude="$(MSBuildThisFileDirectory)SponsorLink/bin/**;$(MSBuildThisFileDirectory)SponsorLink/obj/**"
-                      Source="SponsorLink"
-                      ManifestResourceName="Devlooped.%(Filename)"/>
-    <None Include="$(MSBuildThisFileDirectory)SponsorLink/buildTransitive/*.*"
-          Source="SponsorLink"
-          PackagePath="buildTransitive/%(Filename)%(Extension)"/>
-  </ItemGroup>
-
-  <ItemGroup Condition="$(ShowSponsorLinkInProject) == 'true'">
-    <Compile Update="@(Compile -> WithMetadataValue('Source', 'SponsorLink'))">
-      <Link>SponsorLink\%(RecursiveDir)%(Filename)%(Extension)</Link>
-    </Compile>
-    <EmbeddedResource Update="@(EmbeddedResource -> WithMetadataValue('Source', 'SponsorLink'))">
-      <Link>SponsorLink\%(RecursiveDir)%(Filename)%(Extension)</Link>
-    </EmbeddedResource>
-    <Content Update="@(Content -> WithMetadataValue('Source', 'SponsorLink'))">
-      <Link>SponsorLink\%(RecursiveDir)%(Filename)%(Extension)</Link>
-    </Content>
-    <None Update="@(None -> WithMetadataValue('Source', 'SponsorLink'))">
-      <Link>SponsorLink\%(PackagePath)</Link>
-    </None>
-  </ItemGroup>
-
-  <ItemGroup Condition="$(ShowSponsorLinkInProject) != 'true'">
-    <Compile Update="@(Compile -> WithMetadataValue('Source', 'SponsorLink'))">
-      <Visible>false</Visible>
-    </Compile>
-    <EmbeddedResource Update="@(EmbeddedResource -> WithMetadataValue('Source', 'SponsorLink'))">
-      <Visible>false</Visible>
-    </EmbeddedResource>
-    <Content Update="@(Content -> WithMetadataValue('Source', 'SponsorLink'))">
-      <Visible>false</Visible>
-    </Content>
-    <None Update="@(None -> WithMetadataValue('Source', 'SponsorLink'))">
-      <Visible>false</Visible>
-    </None>
-  </ItemGroup>
-
-  <ItemGroup Condition="'$(ManagePackageVersionsCentrally)' == 'true'">
-    <PackageReference Include="Humanizer.Core" VersionOverride="2.14.1" PrivateAssets="all" Pack="false" />
-    <PackageReference Include="Humanizer.Core.es" VersionOverride="2.14.1" PrivateAssets="all" />
-    <PackageReference Include="System.IdentityModel.Tokens.Jwt" VersionOverride="7.6.0" PrivateAssets="all" Pack="false" />
-    <PackageReference Include="ILRepack" Version="2.0.33" VersionOverride="all" PrivateAssets="all" Pack="false" />
-  </ItemGroup>
-
-  <ItemGroup Condition="'$(ManagePackageVersionsCentrally)' != 'true'">
-    <PackageReference Include="Humanizer.Core" Version="2.14.1" PrivateAssets="all" Pack="false" />
-    <PackageReference Include="Humanizer.Core.es" Version="2.14.1" PrivateAssets="all" />
-    <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.6.0" PrivateAssets="all" Pack="false" />
-    <PackageReference Include="ILRepack" Version="2.0.33" PrivateAssets="all"  Pack="false" />
-  </ItemGroup>
-
-  <Target Name="ILRepack" AfterTargets="CoreCompile" BeforeTargets="CopyFilesToOutputDirectory"
-          Inputs="@(IntermediateAssembly -&gt; '%(FullPath)')"
-          Outputs="$(IntermediateOutputPath)ilrepack.txt"
-          Returns="@(MergedAssemblies)"
-          Condition="Exists(@(IntermediateAssembly -&gt; '%(FullPath)')) And '$(MergeAnalyzerAssemblies)' == 'true'">
-    <ItemGroup>
-      <ReferenceCopyLocalAssemblies Include="@(ReferenceCopyLocalPaths)" Condition="'%(Extension)' == '.dll' 
-        And !$([MSBuild]::ValueOrDefault('%(FileName)', '').EndsWith('.resources', StringComparison.OrdinalIgnoreCase))" />
-      <MergedAssemblies Include="@(ReferenceCopyLocalAssemblies)" Condition="
-        !$([MSBuild]::ValueOrDefault('%(FileName)', '').StartsWith('Microsoft.CodeAnalysis', StringComparison.OrdinalIgnoreCase)) And
-        !$([MSBuild]::ValueOrDefault('%(FileName)', '').StartsWith('Microsoft.CSharp', StringComparison.OrdinalIgnoreCase)) And
-        !$([MSBuild]::ValueOrDefault('%(FileName)', '').StartsWith('System.', StringComparison.OrdinalIgnoreCase))"
-      />
-      <!-- Brings in System/Microsoft.IdentityModel, System.Text.Encodings.Web, System.Text.Json  -->
-      <MergedAssemblies Include="@(ReferenceCopyLocalAssemblies)" Condition="
-        $([MSBuild]::ValueOrDefault('%(FileName)', '').StartsWith('System.IdentityModel', StringComparison.OrdinalIgnoreCase)) Or
-        $([MSBuild]::ValueOrDefault('%(FileName)', '').StartsWith('Microsoft.IdentityModel', StringComparison.OrdinalIgnoreCase)) Or
-        $([MSBuild]::ValueOrDefault('%(FileName)', '').StartsWith('System.Text', StringComparison.OrdinalIgnoreCase))"
-      />
-    </ItemGroup>
-    <ItemGroup>
-      <ReferenceCopyLocalDirs Include="@(ReferenceCopyLocalPaths -&gt; '%(RootDir)%(Directory)')" />
-      <ReferenceCopyLocalPaths Remove="@(MergedAssemblies)" />
-      <LibDir Include="@(ReferenceCopyLocalDirs -&gt; Distinct())" />
-    </ItemGroup>
-    <PropertyGroup>
-      <AbsoluteAssemblyOriginatorKeyFile Condition="'$(AssemblyOriginatorKeyFile)' != ''">$([System.IO.Path]::GetFullPath($([System.IO.Path]::Combine('$(MSBuildProjectDirectory)','$(AssemblyOriginatorKeyFile)'))))</AbsoluteAssemblyOriginatorKeyFile>
-      <ILRepackArgs Condition="'$(AbsoluteAssemblyOriginatorKeyFile)' != ''">/keyfile:"$(AbsoluteAssemblyOriginatorKeyFile)" /delaysign</ILRepackArgs>
-      <ILRepackArgs>$(ILRepackArgs) /internalize</ILRepackArgs>
-      <ILRepackArgs>$(ILRepackArgs) /union</ILRepackArgs>
-      <!-- This is needed to merge types with identical names into one, wich happens with IFluentInterface in Merq and Merq.Core (Xamarin.Messaging dependencies) -->
-      <ILRepackArgs>$(ILRepackArgs) @(LibDir -&gt; '/lib:"%(Identity)."', ' ')</ILRepackArgs>
-      <ILRepackArgs>$(ILRepackArgs) /out:"@(IntermediateAssembly -&gt; '%(FullPath)')"</ILRepackArgs>
-      <ILRepackArgs>$(ILRepackArgs) "@(IntermediateAssembly -&gt; '%(FullPath)')"</ILRepackArgs>
-      <ILRepackArgs>$(ILRepackArgs) @(MergedAssemblies -&gt; '"%(FullPath)"', ' ')</ILRepackArgs>
-      <!--<ILRepackArgs>$(ILRepackArgs) "/lib:$(NetstandardDirectory)"</ILRepackArgs> -->
-      <!-- This is needed for ilrepack to find netstandard.dll, which is referenced by the System.Text.Json assembly -->
-    </PropertyGroup>
-    <Exec Command="&quot;$(ILRepack)&quot; $(ILRepackArgs)" WorkingDirectory="$(MSBuildProjectDirectory)\$(OutputPath)" StandardErrorImportance="high" StandardOutputImportance="low" ConsoleToMSBuild="true" ContinueOnError="true">
-      <Output TaskParameter="ConsoleOutput" PropertyName="ILRepackOutput" />
-      <Output TaskParameter="ExitCode" PropertyName="ExitCode" />
-    </Exec>
-    <Message Importance="high" Text="$(ILRepackOutput)" Condition="'$(ExitCode)' != '0'" />
-    <Delete Files="$(IntermediateOutputPath)ilrepack.txt" Condition="'$(ExitCode)' != '0'" />
-    <Touch AlwaysCreate="true" Files="$(IntermediateOutputPath)ilrepack.txt" Condition="'$(ExitCode)' == '0'" />
-    <Error Text="$(ILRepackOutput)" Condition="'$(ExitCode)' != '0' And '$(ContinueOnError)' != 'true'" />
-    <ItemGroup>
-      <MergedAssembliesToRemove Include="@(MergedAssemblies)" />
-      <MergedAssembliesToRemove Remove="@(ReferenceToPreserve)" />
-    </ItemGroup>
-    <Delete Files="@(MergedAssembliesToRemove -&gt; '$(MSBuildProjectDirectory)\$(OutputPath)%(Filename)%(Extension)')" Condition="Exists('$(MSBuildProjectDirectory)\$(OutputPath)%(Filename)%(Extension)')" />
-  </Target>
-
-</Project>
\ No newline at end of file
diff --git a/src/SponsorLink/SponsorLink/AppDomainDictionary.cs b/src/SponsorLink/SponsorLink/AppDomainDictionary.cs
deleted file mode 100644
index 05cc949..0000000
--- a/src/SponsorLink/SponsorLink/AppDomainDictionary.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-// <autogenerated />
-#nullable enable
-using System;
-
-namespace Devlooped.Sponsors;
-
-/// <summary>
-/// A helper class to store and retrieve values from the current <see cref="AppDomain"/> 
-/// as typed named values.
-/// </summary>
-/// <remarks>
-/// This allows tools that run within the same app domain to share state, such as 
-/// MSBuild tasks or Roslyn analyzers.
-/// </remarks>
-static class AppDomainDictionary
-{
-    /// <summary>
-    /// Gets the value associated with the specified name, or creates a new one if it doesn't exist.
-    /// </summary>
-    public static TValue Get<TValue>(string name) where TValue : notnull, new()
-    {
-        var data = AppDomain.CurrentDomain.GetData(name);
-        if (data is TValue firstTry)
-            return firstTry;
-
-        lock (AppDomain.CurrentDomain)
-        {
-            if (AppDomain.CurrentDomain.GetData(name) is TValue secondTry)
-                return secondTry;
-
-            var newValue = new TValue();
-            AppDomain.CurrentDomain.SetData(name, newValue);
-            return newValue;
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/SponsorLink/SponsorLink/DiagnosticsManager.cs b/src/SponsorLink/SponsorLink/DiagnosticsManager.cs
deleted file mode 100644
index 49143d9..0000000
--- a/src/SponsorLink/SponsorLink/DiagnosticsManager.cs
+++ /dev/null
@@ -1,138 +0,0 @@
-// <autogenerated />
-#nullable enable
-using System;
-using System.Collections.Concurrent;
-using Humanizer;
-using Microsoft.CodeAnalysis;
-
-namespace Devlooped.Sponsors;
-
-/// <summary>
-/// Manages diagnostics for the SponsorLink analyzer so that there are no duplicates 
-/// when multiple projects share the same product name (i.e. ThisAssembly).
-/// </summary>
-class DiagnosticsManager
-{
-    /// <summary>
-    /// Acceses the diagnostics dictionary for the current <see cref="AppDomain"/>.
-    /// </summary>
-    ConcurrentDictionary<string, Diagnostic> Diagnostics
-    {
-        get => AppDomainDictionary.Get<ConcurrentDictionary<string, Diagnostic>>(nameof(Diagnostics));
-    }
-
-    /// <summary>
-    /// Creates a descriptor from well-known diagnostic kinds.
-    /// </summary>
-    /// <param name="sponsorable">The names of the sponsorable accounts that can be funded for the given product.</param>
-    /// <param name="product">The product or project developed by the sponsorable(s).</param>
-    /// <param name="prefix">Custom prefix to use for diagnostic IDs.</param>
-    /// <param name="status">The kind of status diagnostic to create.</param>
-    /// <returns>The given <see cref="DiagnosticDescriptor"/>.</returns>
-    /// <exception cref="NotImplementedException">The <paramref name="status"/> is not one of the known ones.</exception>
-    public DiagnosticDescriptor GetDescriptor(string[] sponsorable, string product, string prefix, SponsorStatus status) => status switch
-    {
-        SponsorStatus.Unknown => CreateUnknown(sponsorable, product, prefix),
-        SponsorStatus.Sponsor => CreateSponsor(sponsorable, prefix),
-        SponsorStatus.Expiring => CreateExpiring(sponsorable, prefix),
-        SponsorStatus.Expired => CreateExpired(sponsorable, prefix),
-        _ => throw new NotImplementedException(),
-    };
-
-    /// <summary>
-    /// Pushes a diagnostic for the given product. If an existing one exists, it is replaced.
-    /// </summary>
-    /// <returns>The same diagnostic that was pushed, for chained invocations.</returns>
-    public Diagnostic Push(string product, Diagnostic diagnostic)
-    {
-        // Directly sets, since we only expect to get one warning per sponsorable+product 
-        // combination.
-        Diagnostics[product] = diagnostic;
-        return diagnostic;
-    }
-
-    /// <summary>
-    /// Attemps to remove a diagnostic for the given product.
-    /// </summary>
-    /// <param name="product">The product diagnostic that might have been pushed previously.</param>
-    /// <returns>The removed diagnostic, or <see langword="null" /> if none was previously pushed.</returns>
-    public Diagnostic? Pop(string product)
-    {
-        Diagnostics.TryRemove(product, out var diagnostic);
-        return diagnostic;
-    }
-
-    /// <summary>
-    /// Gets the status of the given product based on a previously stored diagnostic.
-    /// </summary>
-    /// <param name="product">The product to check status for.</param>
-    /// <returns>Optional <see cref="SponsorStatus"/> that was reported, if any.</returns>
-    public SponsorStatus? GetStatus(string product)
-    {
-        // NOTE: the SponsorLinkAnalyzer.SetStatus uses diagnostic properties to store the 
-        // kind of diagnostic as a simple string instead of the enum. We do this so that 
-        // multiple analyzers or versions even across multiple products, which all would 
-        // have their own enum, can still share the same diagnostic kind.
-        if (Diagnostics.TryGetValue(product, out var diagnostic) &&
-            diagnostic.Properties.TryGetValue(nameof(SponsorStatus), out var value))
-        {
-            // Switch on value matching DiagnosticKind names
-            return value switch
-            {
-                nameof(SponsorStatus.Unknown) => SponsorStatus.Unknown,
-                nameof(SponsorStatus.Sponsor) => SponsorStatus.Sponsor,
-                nameof(SponsorStatus.Expiring) => SponsorStatus.Expiring,
-                nameof(SponsorStatus.Expired) => SponsorStatus.Expired,
-                _ => null,
-            };
-        }
-
-        return null;
-    }
-
-    static DiagnosticDescriptor CreateSponsor(string[] sponsorable, string prefix) => new(
-        $"{prefix}100",
-        ThisAssembly.Strings.Sponsor.Title,
-        ThisAssembly.Strings.Sponsor.MessageFormat,
-        "SponsorLink",
-        DiagnosticSeverity.Info,
-        isEnabledByDefault: true,
-        description: ThisAssembly.Strings.Sponsor.Description,
-        helpLinkUri: "https://github.com/devlooped#sponsorlink",
-        "DoesNotSupportF1Help");
-
-    static DiagnosticDescriptor CreateUnknown(string[] sponsorable, string product, string prefix) => new(
-        $"{prefix}101",
-        ThisAssembly.Strings.Unknown.Title,
-        ThisAssembly.Strings.Unknown.MessageFormat,
-        "SponsorLink",
-        DiagnosticSeverity.Warning,
-        isEnabledByDefault: true,
-        description: ThisAssembly.Strings.Unknown.Description(
-            sponsorable.Humanize(x => $"https://github.com/sponsors/{x}"),
-            string.Join(" ", sponsorable)),
-        helpLinkUri: "https://github.com/devlooped#sponsorlink",
-        WellKnownDiagnosticTags.NotConfigurable);
-
-    static DiagnosticDescriptor CreateExpiring(string[] sponsorable, string prefix) => new(
-         $"{prefix}103",
-         ThisAssembly.Strings.Expiring.Title,
-         ThisAssembly.Strings.Expiring.MessageFormat,
-         "SponsorLink",
-         DiagnosticSeverity.Warning,
-         isEnabledByDefault: true,
-         description: ThisAssembly.Strings.Expiring.Description(string.Join(" ", sponsorable)),
-         helpLinkUri: "https://github.com/devlooped#autosync",
-         "DoesNotSupportF1Help", WellKnownDiagnosticTags.NotConfigurable);
-
-    static DiagnosticDescriptor CreateExpired(string[] sponsorable, string prefix) => new(
-         $"{prefix}104",
-         ThisAssembly.Strings.Expired.Title,
-         ThisAssembly.Strings.Expired.MessageFormat,
-         "SponsorLink",
-         DiagnosticSeverity.Warning,
-         isEnabledByDefault: true,
-         description: ThisAssembly.Strings.Expired.Description(string.Join(" ", sponsorable)),
-         helpLinkUri: "https://github.com/devlooped#autosync",
-         "DoesNotSupportF1Help", WellKnownDiagnosticTags.NotConfigurable);
-}
diff --git a/src/SponsorLink/SponsorLink/ManifestStatus.cs b/src/SponsorLink/SponsorLink/ManifestStatus.cs
deleted file mode 100644
index 0960e5a..0000000
--- a/src/SponsorLink/SponsorLink/ManifestStatus.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-// <autogenerated />
-namespace Devlooped.Sponsors;
-
-/// <summary>
-/// The resulting status from validation.
-/// </summary>
-public enum ManifestStatus
-{
-    /// <summary>
-    /// The manifest couldn't be read at all.
-    /// </summary>
-    Unknown,
-    /// <summary>
-    /// The manifest was read and is valid (not expired and properly signed).
-    /// </summary>
-    Valid,
-    /// <summary>
-    /// The manifest was read but has expired.
-    /// </summary>
-    Expired,
-    /// <summary>
-    /// The manifest was read, but its signature is invalid.
-    /// </summary>
-    Invalid,
-}
diff --git a/src/SponsorLink/SponsorLink/SponsorLink.cs b/src/SponsorLink/SponsorLink/SponsorLink.cs
deleted file mode 100644
index a5e5beb..0000000
--- a/src/SponsorLink/SponsorLink/SponsorLink.cs
+++ /dev/null
@@ -1,169 +0,0 @@
-// <autogenerated />
-#nullable enable
-using System;
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-using System.IdentityModel.Tokens.Jwt;
-using System.Linq;
-using System.Reflection;
-using System.Security.Claims;
-using Microsoft.IdentityModel.Tokens;
-
-namespace Devlooped.Sponsors;
-
-static partial class SponsorLink
-{
-    public static Dictionary<string, string> Sponsorables { get; } = typeof(SponsorLink).Assembly
-        .GetCustomAttributes<AssemblyMetadataAttribute>()
-        .Where(x => x.Key.StartsWith("Funding.GitHub."))
-        .Select(x => new { Key = x.Key[15..], x.Value })
-        .ToDictionary(x => x.Key, x => x.Value);
-
-    /// <summary>
-    /// Whether the current process is running in an IDE, either 
-    /// <see cref="IsVisualStudio"/> or <see cref="IsRider"/>.
-    /// </summary>
-    public static bool IsEditor => IsVisualStudio || IsRider;
-
-    /// <summary>
-    /// Whether the current process is running as part of an active Visual Studio instance.
-    /// </summary>
-    public static bool IsVisualStudio =>
-        Environment.GetEnvironmentVariable("ServiceHubLogSessionKey") != null ||
-        Environment.GetEnvironmentVariable("VSAPPIDNAME") != null;
-
-    /// <summary>
-    /// Whether the current process is running as part of an active Rider instance.
-    /// </summary>
-    public static bool IsRider =>
-        Environment.GetEnvironmentVariable("RESHARPER_FUS_SESSION") != null ||
-        Environment.GetEnvironmentVariable("IDEA_INITIAL_DIRECTORY") != null;
-
-    /// <summary>
-    /// Manages the sharing and reporting of diagnostics across the source generator 
-    /// and the diagnostic analyzer, to avoid doing the online check more than once.
-    /// </summary>
-    public static DiagnosticsManager Diagnostics { get; } = new();
-
-    /// <summary>
-    /// Gets the expiration date from the principal, if any.
-    /// </summary>
-    /// <returns>
-    /// Whichever "exp" claim is the latest, or <see langword="null"/> if none found.
-    /// </returns>
-    public static DateTime? GetExpiration(this ClaimsPrincipal principal)
-        // get all "exp" claims, parse them and return the latest one or null if none found
-        => principal.FindAll("exp")
-            .Select(c => c.Value)
-            .Select(long.Parse)
-            .Select(DateTimeOffset.FromUnixTimeSeconds)
-            .Max().DateTime is var exp && exp == DateTime.MinValue ? null : exp;
-
-    /// <summary>
-    /// Reads all manifests, validating their signatures.
-    /// </summary>
-    /// <param name="principal">The combined principal with all identities (and their claims) from each provided and valid JWT</param>
-    /// <param name="values">The tokens to read and their corresponding JWK for signature verification.</param>
-    /// <returns><see langword="true"/> if at least one manifest can be successfully read and is valid. 
-    /// <see langword="false"/> otherwise.</returns>
-    public static bool TryRead([NotNullWhen(true)] out ClaimsPrincipal? principal, params (string jwt, string jwk)[] values)
-        => TryRead(out principal, values.AsEnumerable());
-
-    /// <summary>
-    /// Reads all manifests, validating their signatures.
-    /// </summary>
-    /// <param name="principal">The combined principal with all identities (and their claims) from each provided and valid JWT</param>
-    /// <param name="values">The tokens to read and their corresponding JWK for signature verification.</param>
-    /// <returns><see langword="true"/> if at least one manifest can be successfully read and is valid. 
-    /// <see langword="false"/> otherwise.</returns>
-    public static bool TryRead([NotNullWhen(true)] out ClaimsPrincipal? principal, IEnumerable<(string jwt, string jwk)> values)
-    {
-        principal = null;
-
-        foreach (var value in values)
-        {
-            if (string.IsNullOrWhiteSpace(value.jwk) || string.IsNullOrEmpty(value.jwk))
-                continue;
-
-            if (Validate(value.jwt, value.jwk, out var token, out var claims, false) == ManifestStatus.Valid && claims != null)
-            {
-                if (principal == null)
-                    principal = claims;
-                else
-                    principal.AddIdentities(claims.Identities);
-            }
-        }
-
-        return principal != null;
-    }
-
-    /// <summary>
-    /// Validates the manifest signature and optional expiration.
-    /// </summary>
-    /// <param name="jwt">The JWT to validate.</param>
-    /// <param name="jwk">The key to validate the manifest signature with.</param>
-    /// <param name="token">Except when returning <see cref="Status.Unknown"/>, returns the security token read from the JWT, even if signature check failed.</param>
-    /// <param name="principal">The associated claims, only when return value is not <see cref="Status.Unknown"/>.</param>
-    /// <param name="requireExpiration">Whether to check for expiration.</param>
-    /// <returns>The status of the validation.</returns>
-    public static ManifestStatus Validate(string jwt, string jwk, out SecurityToken? token, out ClaimsPrincipal? principal, bool validateExpiration)
-    {
-        token = default;
-        principal = default;
-
-        SecurityKey key;
-        try
-        {
-            key = JsonWebKey.Create(jwk);
-        }
-        catch (ArgumentException)
-        {
-            return ManifestStatus.Unknown;
-        }
-
-        var handler = new JwtSecurityTokenHandler { MapInboundClaims = false };
-
-        if (!handler.CanReadToken(jwt))
-            return ManifestStatus.Unknown;
-
-        var validation = new TokenValidationParameters
-        {
-            RequireExpirationTime = false,
-            ValidateLifetime = false,
-            ValidateAudience = false,
-            ValidateIssuer = false,
-            ValidateIssuerSigningKey = true,
-            IssuerSigningKey = key,
-            RoleClaimType = "roles",
-            NameClaimType = "sub",
-        };
-
-        try
-        {
-            principal = handler.ValidateToken(jwt, validation, out token);
-            if (validateExpiration && token.ValidTo == DateTime.MinValue)
-                return ManifestStatus.Invalid;
-
-            // The sponsorable manifest does not have an expiration time.
-            if (validateExpiration && token.ValidTo < DateTimeOffset.UtcNow)
-                return ManifestStatus.Expired;
-
-            return ManifestStatus.Valid;
-        }
-        catch (SecurityTokenInvalidSignatureException)
-        {
-            var jwtToken = handler.ReadJwtToken(jwt);
-            token = jwtToken;
-            principal = new ClaimsPrincipal(new ClaimsIdentity(jwtToken.Claims));
-            return ManifestStatus.Invalid;
-        }
-        catch (SecurityTokenException)
-        {
-            var jwtToken = handler.ReadJwtToken(jwt);
-            token = jwtToken;
-            principal = new ClaimsPrincipal(new ClaimsIdentity(jwtToken.Claims));
-            return ManifestStatus.Invalid;
-        }
-    }
-
-}
diff --git a/src/SponsorLink/SponsorLink/SponsorLink.csproj b/src/SponsorLink/SponsorLink/SponsorLink.csproj
deleted file mode 100644
index 4b00feb..0000000
--- a/src/SponsorLink/SponsorLink/SponsorLink.csproj
+++ /dev/null
@@ -1,46 +0,0 @@
-<Project Sdk="Microsoft.NET.Sdk">
-
-  <PropertyGroup>
-    <TargetFramework>netstandard2.0</TargetFramework>
-    <Product>SponsorLink</Product>
-    <ImplicitUsings>disable</ImplicitUsings>
-    <GenerateDocumentationFile>false</GenerateDocumentationFile>
-  </PropertyGroup>
-
-  <PropertyGroup Label="SponsorLink">
-    <DevloopedJwk>$([System.IO.File]::ReadAllText('$(MSBuildThisFileDirectory)devlooped.pub.jwk'))</DevloopedJwk>
-    <!-- Default funding product the Product, which already part of ThisAssembly -->
-    <FundingProduct Condition="'$(FundingProduct)' == ''">$(Product)</FundingProduct>
-    <!-- Default prefix is the joined upper-case letters in the product name (i.e. for ThisAssembly, TA) -->
-    <FundingPrefix Condition="'$(FundingPrefix)' == ''">$([System.Text.RegularExpressions.Regex]::Replace("$(FundingProduct)", "[^A-Z]", ""))</FundingPrefix>
-    <!-- Default grace days for an expired sponsor manifest -->
-    <FundingGrace Condition="'$(FundingGrace)' == ''">21</FundingGrace>
-  </PropertyGroup>
-
-  <ItemGroup>
-    <PackageReference Include="Humanizer.Core" Version="2.14.1" />
-    <PackageReference Include="Humanizer.Core.es" Version="2.14.1" />
-    <PackageReference Include="ILRepack" Version="2.0.33" PrivateAssets="all" />
-    <PackageReference Include="NuGetizer" Version="1.2.2" />
-    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" Pack="false" />
-    <PackageReference Include="PolySharp" Version="1.14.1" PrivateAssets="all" />
-    <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.6.0" PrivateAssets="all" />
-    <PackageReference Include="ThisAssembly.Constants" Version="1.4.3" PrivateAssets="all" />
-    <PackageReference Include="ThisAssembly.Git" Version="1.4.3" PrivateAssets="all" />
-    <PackageReference Include="ThisAssembly.Strings" Version="1.4.3" PrivateAssets="all" />
-    <PackageReference Include="ThisAssembly.Project" Version="1.4.3" PrivateAssets="all" />
-  </ItemGroup>
-
-  <ItemGroup>
-    <EmbeddedResource Update="SponsorLink.es.resx" ManifestResourceName="Devlooped.%(Filename)" />
-    <EmbeddedResource Update="SponsorLink.resx" ManifestResourceName="Devlooped.%(Filename)" />
-  </ItemGroup>
-
-  <ItemGroup>
-    <AssemblyMetadata Include="Funding.GitHub.devlooped" Value="$(DevloopedJwk)" />
-    <Constant Include="Funding.Product" Value="$(FundingProduct)" />
-    <Constant Include="Funding.Prefix" Value="$(FundingPrefix)" />
-    <Constant Include="Funding.Grace" Value="$(FundingGrace)" />
-  </ItemGroup>
-
-</Project>
diff --git a/src/SponsorLink/SponsorLink/SponsorLink.es.resx b/src/SponsorLink/SponsorLink/SponsorLink.es.resx
deleted file mode 100644
index d8794ca..0000000
--- a/src/SponsorLink/SponsorLink/SponsorLink.es.resx
+++ /dev/null
@@ -1,163 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<root>
-  <!-- 
-    Microsoft ResX Schema 
-    
-    Version 2.0
-    
-    The primary goals of this format is to allow a simple XML format 
-    that is mostly human readable. The generation and parsing of the 
-    various data types are done through the TypeConverter classes 
-    associated with the data types.
-    
-    Example:
-    
-    ... ado.net/XML headers & schema ...
-    <resheader name="resmimetype">text/microsoft-resx</resheader>
-    <resheader name="version">2.0</resheader>
-    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
-    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
-    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
-    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
-    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
-        <value>[base64 mime encoded serialized .NET Framework object]</value>
-    </data>
-    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
-        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
-        <comment>This is a comment</comment>
-    </data>
-                
-    There are any number of "resheader" rows that contain simple 
-    name/value pairs.
-    
-    Each data row contains a name, and value. The row also contains a 
-    type or mimetype. Type corresponds to a .NET class that support 
-    text/value conversion through the TypeConverter architecture. 
-    Classes that don't support this are serialized and stored with the 
-    mimetype set.
-    
-    The mimetype is used for serialized objects, and tells the 
-    ResXResourceReader how to depersist the object. This is currently not 
-    extensible. For a given mimetype the value must be set accordingly:
-    
-    Note - application/x-microsoft.net.object.binary.base64 is the format 
-    that the ResXResourceWriter will generate, however the reader can 
-    read any of the formats listed below.
-    
-    mimetype: application/x-microsoft.net.object.binary.base64
-    value   : The object must be serialized with 
-            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
-            : and then encoded with base64 encoding.
-    
-    mimetype: application/x-microsoft.net.object.soap.base64
-    value   : The object must be serialized with 
-            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
-            : and then encoded with base64 encoding.
-
-    mimetype: application/x-microsoft.net.object.bytearray.base64
-    value   : The object must be serialized into a byte array 
-            : using a System.ComponentModel.TypeConverter
-            : and then encoded with base64 encoding.
-    -->
-  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
-    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
-    <xsd:element name="root" msdata:IsDataSet="true">
-      <xsd:complexType>
-        <xsd:choice maxOccurs="unbounded">
-          <xsd:element name="metadata">
-            <xsd:complexType>
-              <xsd:sequence>
-                <xsd:element name="value" type="xsd:string" minOccurs="0" />
-              </xsd:sequence>
-              <xsd:attribute name="name" use="required" type="xsd:string" />
-              <xsd:attribute name="type" type="xsd:string" />
-              <xsd:attribute name="mimetype" type="xsd:string" />
-              <xsd:attribute ref="xml:space" />
-            </xsd:complexType>
-          </xsd:element>
-          <xsd:element name="assembly">
-            <xsd:complexType>
-              <xsd:attribute name="alias" type="xsd:string" />
-              <xsd:attribute name="name" type="xsd:string" />
-            </xsd:complexType>
-          </xsd:element>
-          <xsd:element name="data">
-            <xsd:complexType>
-              <xsd:sequence>
-                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
-                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
-              </xsd:sequence>
-              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
-              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
-              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
-              <xsd:attribute ref="xml:space" />
-            </xsd:complexType>
-          </xsd:element>
-          <xsd:element name="resheader">
-            <xsd:complexType>
-              <xsd:sequence>
-                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
-              </xsd:sequence>
-              <xsd:attribute name="name" type="xsd:string" use="required" />
-            </xsd:complexType>
-          </xsd:element>
-        </xsd:choice>
-      </xsd:complexType>
-    </xsd:element>
-  </xsd:schema>
-  <resheader name="resmimetype">
-    <value>text/microsoft-resx</value>
-  </resheader>
-  <resheader name="version">
-    <value>2.0</value>
-  </resheader>
-  <resheader name="reader">
-    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </resheader>
-  <resheader name="writer">
-    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </resheader>
-  <data name="Unknown_Description" xml:space="preserve">
-    <value>Patrocinar los proyectos en que dependes asegura que se mantengan activos, y que recibas el apoyo que necesitas. También es muy económico y está disponible en todo el mundo!
-Por favor considera apoyar el proyecto patrocinando en {links} y ejecutando posteriormente 'gh sponsors sync {spaced}'.</value>
-  </data>
-  <data name="Unknown_Message" xml:space="preserve">
-    <value>No se pudo determinar el estado de su patrocinio. Funcionalidades exclusivas para patrocinadores pueden no estar disponibles.</value>
-  </data>
-  <data name="Unknown_Title" xml:space="preserve">
-    <value>Estado de patrocinio desconocido</value>
-  </data>
-  <data name="Expired_Description" xml:space="preserve">
-    <value>Funcionalidades exclusivas para patrocinadores pueden no estar disponibles. Ejecuta 'gh sponsors sync {spaced}' y, opcionalmente, habilita la sincronización automática.</value>
-  </data>
-  <data name="Expired_Message" xml:space="preserve">
-    <value>El estado de patrocino ha expirado y la sincronización automática no está habilitada.</value>
-  </data>
-  <data name="Expired_Title" xml:space="preserve">
-    <value>El estado de patrocino ha expirado</value>
-  </data>
-  <data name="Sponsor_Description" xml:space="preserve">
-    <value>Eres un verdadero héroe. Tu patrocinio ayuda a mantener el proyecto vivo y próspero 🙏.</value>
-  </data>
-  <data name="Sponsor_Message" xml:space="preserve">
-    <value>Gracias por apoyar a {0} con tu patrocinio de {1} 💟!</value>
-  </data>
-  <data name="Sponsor_Title" xml:space="preserve">
-    <value>Eres un patrocinador del proyecto, eres lo máximo 💟!</value>
-  </data>
-  <data name="Expiring_Description" xml:space="preserve">
-    <value>El estado de patrocino ha expirado y estás en un período de gracia. Ejecuta 'gh sponsors sync {spaced}' y, opcionalmente, habilita la sincronización automática.</value>
-  </data>
-  <data name="Expiring_Message" xml:space="preserve">
-    <value>El estado de patrocino necesita actualización periódica y la sincronización automática no está habilitada.</value>
-  </data>
-  <data name="Expiring_Title" xml:space="preserve">
-    <value>El estado de patrocino ha expirado y el período de gracia terminará pronto</value>
-  </data>
-  <data name="And" xml:space="preserve">
-    <value>y</value>
-  </data>
-  <data name="Or" xml:space="preserve">
-    <value>o</value>
-  </data>
-</root>
\ No newline at end of file
diff --git a/src/SponsorLink/SponsorLink/SponsorLink.resx b/src/SponsorLink/SponsorLink/SponsorLink.resx
deleted file mode 100644
index b8cdd5e..0000000
--- a/src/SponsorLink/SponsorLink/SponsorLink.resx
+++ /dev/null
@@ -1,164 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<root>
-  <!-- 
-    Microsoft ResX Schema 
-    
-    Version 2.0
-    
-    The primary goals of this format is to allow a simple XML format 
-    that is mostly human readable. The generation and parsing of the 
-    various data types are done through the TypeConverter classes 
-    associated with the data types.
-    
-    Example:
-    
-    ... ado.net/XML headers & schema ...
-    <resheader name="resmimetype">text/microsoft-resx</resheader>
-    <resheader name="version">2.0</resheader>
-    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
-    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
-    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
-    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
-    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
-        <value>[base64 mime encoded serialized .NET Framework object]</value>
-    </data>
-    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
-        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
-        <comment>This is a comment</comment>
-    </data>
-                
-    There are any number of "resheader" rows that contain simple 
-    name/value pairs.
-    
-    Each data row contains a name, and value. The row also contains a 
-    type or mimetype. Type corresponds to a .NET class that support 
-    text/value conversion through the TypeConverter architecture. 
-    Classes that don't support this are serialized and stored with the 
-    mimetype set.
-    
-    The mimetype is used for serialized objects, and tells the 
-    ResXResourceReader how to depersist the object. This is currently not 
-    extensible. For a given mimetype the value must be set accordingly:
-    
-    Note - application/x-microsoft.net.object.binary.base64 is the format 
-    that the ResXResourceWriter will generate, however the reader can 
-    read any of the formats listed below.
-    
-    mimetype: application/x-microsoft.net.object.binary.base64
-    value   : The object must be serialized with 
-            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
-            : and then encoded with base64 encoding.
-    
-    mimetype: application/x-microsoft.net.object.soap.base64
-    value   : The object must be serialized with 
-            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
-            : and then encoded with base64 encoding.
-
-    mimetype: application/x-microsoft.net.object.bytearray.base64
-    value   : The object must be serialized into a byte array 
-            : using a System.ComponentModel.TypeConverter
-            : and then encoded with base64 encoding.
-    -->
-  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
-    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
-    <xsd:element name="root" msdata:IsDataSet="true">
-      <xsd:complexType>
-        <xsd:choice maxOccurs="unbounded">
-          <xsd:element name="metadata">
-            <xsd:complexType>
-              <xsd:sequence>
-                <xsd:element name="value" type="xsd:string" minOccurs="0" />
-              </xsd:sequence>
-              <xsd:attribute name="name" use="required" type="xsd:string" />
-              <xsd:attribute name="type" type="xsd:string" />
-              <xsd:attribute name="mimetype" type="xsd:string" />
-              <xsd:attribute ref="xml:space" />
-            </xsd:complexType>
-          </xsd:element>
-          <xsd:element name="assembly">
-            <xsd:complexType>
-              <xsd:attribute name="alias" type="xsd:string" />
-              <xsd:attribute name="name" type="xsd:string" />
-            </xsd:complexType>
-          </xsd:element>
-          <xsd:element name="data">
-            <xsd:complexType>
-              <xsd:sequence>
-                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
-                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
-              </xsd:sequence>
-              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
-              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
-              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
-              <xsd:attribute ref="xml:space" />
-            </xsd:complexType>
-          </xsd:element>
-          <xsd:element name="resheader">
-            <xsd:complexType>
-              <xsd:sequence>
-                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
-              </xsd:sequence>
-              <xsd:attribute name="name" type="xsd:string" use="required" />
-            </xsd:complexType>
-          </xsd:element>
-        </xsd:choice>
-      </xsd:complexType>
-    </xsd:element>
-  </xsd:schema>
-  <resheader name="resmimetype">
-    <value>text/microsoft-resx</value>
-  </resheader>
-  <resheader name="version">
-    <value>2.0</value>
-  </resheader>
-  <resheader name="reader">
-    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </resheader>
-  <resheader name="writer">
-    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </resheader>
-  <data name="Unknown_Description" xml:space="preserve">
-    <value>Sponsoring projects you depend on ensures they remain active, and that you get the support you need. It's also super affordable and available worldwide! 
-Please consider supporting the project by sponsoring at {links} and running 'gh sponsors sync {spaced}' afterwards.</value>
-    <comment>Unknown sponsor description</comment>
-  </data>
-  <data name="Unknown_Message" xml:space="preserve">
-    <value>Please consider supporting {0} by sponsoring {1} 🙏</value>
-  </data>
-  <data name="Unknown_Title" xml:space="preserve">
-    <value>Unknown sponsor status</value>
-  </data>
-  <data name="Expired_Description" xml:space="preserve">
-    <value>Sponsor-only features may be disabled. Please run 'gh sponsors sync {spaced}' and optionally enable automatic sync.</value>
-  </data>
-  <data name="Expired_Message" xml:space="preserve">
-    <value>Sponsor status has expired and automatic sync has not been enabled.</value>
-  </data>
-  <data name="Expired_Title" xml:space="preserve">
-    <value>Sponsor status expired</value>
-  </data>
-  <data name="Sponsor_Description" xml:space="preserve">
-    <value>You are a true hero. Your sponsorship helps keep the project alive and thriving 🙏.</value>
-  </data>
-  <data name="Sponsor_Message" xml:space="preserve">
-    <value>Thank you for supporting {0} with your sponsorship 💟!</value>
-  </data>
-  <data name="Sponsor_Title" xml:space="preserve">
-    <value>You are a sponsor of the project, you rock 💟!</value>
-  </data>
-  <data name="Expiring_Description" xml:space="preserve">
-    <value>Sponsor status has expired and you are in the grace period. Please run 'gh sponsors sync {spaced}' and optionally enable automatic sync.</value>
-  </data>
-  <data name="Expiring_Message" xml:space="preserve">
-    <value>Sponsor status needs periodic updating and automatic sync has not been enabled.</value>
-  </data>
-  <data name="Expiring_Title" xml:space="preserve">
-    <value>Sponsor status expired, grace period ending soon</value>
-  </data>
-  <data name="And" xml:space="preserve">
-    <value>and</value>
-  </data>
-  <data name="Or" xml:space="preserve">
-    <value>or</value>
-  </data>
-</root>
\ No newline at end of file
diff --git a/src/SponsorLink/SponsorLink/SponsorLinkAnalyzer.cs b/src/SponsorLink/SponsorLink/SponsorLinkAnalyzer.cs
deleted file mode 100644
index 2e97528..0000000
--- a/src/SponsorLink/SponsorLink/SponsorLinkAnalyzer.cs
+++ /dev/null
@@ -1,126 +0,0 @@
-// <autogenerated />
-#nullable enable
-using System;
-using System.Collections.Generic;
-using System.Collections.Immutable;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using Humanizer;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.Diagnostics;
-using static Devlooped.Sponsors.SponsorLink;
-using static ThisAssembly.Constants;
-
-namespace Devlooped.Sponsors;
-
-/// <summary>
-/// Links the sponsor status for the current compilation.
-/// </summary>
-[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
-public class SponsorLinkAnalyzer : DiagnosticAnalyzer
-{
-    static readonly int graceDays = int.Parse(Funding.Grace);
-    static readonly Dictionary<SponsorStatus, DiagnosticDescriptor> descriptors = new()
-    {
-        // Requires:
-        // <Constant Include="Funding.Product" Value="[PRODUCT_NAME]" />
-        // <Constant Include="Funding.AnalyzerPrefix" Value="[PREFIX]" />
-        { SponsorStatus.Unknown, Diagnostics.GetDescriptor([.. Sponsorables.Keys], Funding.Product, Funding.Prefix, SponsorStatus.Unknown) },
-        { SponsorStatus.Sponsor, Diagnostics.GetDescriptor([.. Sponsorables.Keys], Funding.Product, Funding.Prefix, SponsorStatus.Sponsor) },
-        { SponsorStatus.Expiring, Diagnostics.GetDescriptor([.. Sponsorables.Keys], Funding.Product, Funding.Prefix, SponsorStatus.Expiring) },
-        { SponsorStatus.Expired, Diagnostics.GetDescriptor([.. Sponsorables.Keys], Funding.Product, Funding.Prefix, SponsorStatus.Expired) },
-    };
-
-    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = descriptors.Values.ToImmutableArray();
-
-#pragma warning disable RS1026 // Enable concurrent execution
-    public override void Initialize(AnalysisContext context)
-#pragma warning restore RS1026 // Enable concurrent execution
-    {
-#if !DEBUG
-        // Only enable concurrent execution in release builds, otherwise debugging is quite annoying.
-        context.EnableConcurrentExecution();
-#endif
-        context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
-
-#pragma warning disable RS1013 // Start action has no registered non-end actions
-        // We do this so that the status is set at compilation start so we can use it 
-        // across all other analyzers. We report only on finish because multiple 
-        // analyzers can report the same diagnostic and we want to avoid duplicates. 
-        context.RegisterCompilationStartAction(ctx =>
-        {
-            var manifests = ctx.Options.AdditionalFiles
-                .Where(x =>
-                    ctx.Options.AnalyzerConfigOptionsProvider.GetOptions(x).TryGetValue("build_metadata.AdditionalFiles.SourceItemType", out var itemType) &&
-                    itemType == "SponsorManifest" &&
-                    Sponsorables.ContainsKey(Path.GetFileNameWithoutExtension(x.Path)))
-                .ToImmutableArray();
-
-            // Setting the status early allows other analyzers to potentially check for it.
-            var status = SetStatus(manifests);
-            // Never report any diagnostic unless we're in an editor.
-            if (IsEditor)
-            {
-                // NOTE: even if we don't report the diagnostic, we still set the status so other analyzers can use it.
-                ctx.RegisterCompilationEndAction(ctx =>
-                {
-                    if (Diagnostics.Pop(Funding.Product) is Diagnostic diagnostic)
-                    {
-                        ctx.ReportDiagnostic(diagnostic);
-                    }
-                    else
-                    {
-                        // This should never happen and would be a bug.
-                        Debug.Assert(true, "We should have provided a diagnostic of some kind for " + Funding.Product);
-                        // We'll report it as unknown as a fallback for now.
-                        ctx.ReportDiagnostic(Diagnostic.Create(descriptors[SponsorStatus.Unknown], null,
-                            properties: ImmutableDictionary.Create<string, string?>().Add(nameof(SponsorStatus), nameof(SponsorStatus.Unknown)),
-                            Funding.Product, Sponsorables.Keys.Humanize(ThisAssembly.Strings.Or)));
-                    }
-                });
-            }
-        });
-#pragma warning restore RS1013 // Start action has no registered non-end actions
-    }
-
-    SponsorStatus SetStatus(ImmutableArray<AdditionalText> manifests)
-    {
-        if (!SponsorLink.TryRead(out var claims, manifests.Select(text =>
-                (text.GetText()?.ToString() ?? "", Sponsorables[Path.GetFileNameWithoutExtension(text.Path)]))) ||
-            claims.GetExpiration() is not DateTime exp)
-        {
-            // report unknown, either unparsed manifest or one with no expiration (which we never emit).
-            Diagnostics.Push(Funding.Product, Diagnostic.Create(descriptors[SponsorStatus.Unknown], null,
-                properties: ImmutableDictionary.Create<string, string?>().Add(nameof(SponsorStatus), nameof(SponsorStatus.Unknown)),
-                Funding.Product, Sponsorables.Keys.Humanize(ThisAssembly.Strings.Or)));
-            return SponsorStatus.Unknown;
-        }
-        else if (exp < DateTime.Now)
-        {
-            // report expired or expiring soon if still within the configured days of grace period
-            if (exp.AddDays(graceDays) < DateTime.Now)
-            {
-                // report expiring soon
-                Diagnostics.Push(Funding.Product, Diagnostic.Create(descriptors[SponsorStatus.Expiring], null,
-                    properties: ImmutableDictionary.Create<string, string?>().Add(nameof(SponsorStatus), nameof(SponsorStatus.Expiring))));
-                return SponsorStatus.Expiring;
-            }
-            else
-            {
-                // report expired
-                Diagnostics.Push(Funding.Product, Diagnostic.Create(descriptors[SponsorStatus.Expired], null,
-                    properties: ImmutableDictionary.Create<string, string?>().Add(nameof(SponsorStatus), nameof(SponsorStatus.Expired))));
-                return SponsorStatus.Expired;
-            }
-        }
-        else
-        {
-            // report sponsor
-            Diagnostics.Push(Funding.Product, Diagnostic.Create(descriptors[SponsorStatus.Sponsor], null,
-                properties: ImmutableDictionary.Create<string, string?>().Add(nameof(SponsorStatus), nameof(SponsorStatus.Sponsor)),
-                Funding.Product));
-            return SponsorStatus.Sponsor;
-        }
-    }
-}
diff --git a/src/SponsorLink/SponsorLink/SponsorStatus.cs b/src/SponsorLink/SponsorLink/SponsorStatus.cs
deleted file mode 100644
index 6cdbc90..0000000
--- a/src/SponsorLink/SponsorLink/SponsorStatus.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-// <autogenerated />
-namespace Devlooped.Sponsors;
-
-/// <summary>
-/// The determined sponsoring status.
-/// </summary>
-public enum SponsorStatus
-{
-    /// <summary>
-    /// Sponsorship status is unknown.
-    /// </summary>
-    Unknown,
-    /// <summary>
-    /// The sponsors manifest is expired but within the grace period.
-    /// </summary>
-    Expiring,
-    /// <summary>
-    /// The sponsors manifest is expired and outside the grace period.
-    /// </summary>
-    Expired,
-    /// <summary>
-    /// The user is sponsoring.
-    /// </summary>
-    Sponsor,
-}
diff --git a/src/SponsorLink/SponsorLink/SponsorableLib.targets b/src/SponsorLink/SponsorLink/SponsorableLib.targets
deleted file mode 100644
index 8311ca6..0000000
--- a/src/SponsorLink/SponsorLink/SponsorableLib.targets
+++ /dev/null
@@ -1,60 +0,0 @@
-<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-
-  <PropertyGroup>
-    <!-- This showcases how to use a package-provided file as diagnostics linked content -->
-    <SponsorableLibSponsorable>$([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)sponsorable.md))</SponsorableLibSponsorable>
-  </PropertyGroup>
-
-  <ItemGroup>
-    <!-- This makes it visible to the generator -->
-    <CompilerVisibleProperty Include="SponsorableLibSponsorable" />
-  </ItemGroup>
-
-  <PropertyGroup>
-    <!-- This makes sure builds are never broken from sponsorship warnings, even if WarningsAsErrors is used -->
-    <WarningsNotAsErrors>$(WarningsNotAsErrors);LIB001;LIB002;LIB003;LIB004;LIB005</WarningsNotAsErrors>
-
-    <AutoSyncStampFile>$(BaseIntermediateOutputPath)autosync.stamp</AutoSyncStampFile>
-
-    <UserProfileHome Condition="'$([MSBuild]::IsOSUnixLike())' == 'true'">$(HOME)</UserProfileHome>
-    <UserProfileHome Condition="'$([MSBuild]::IsOSUnixLike())' != 'true'">$(USERPROFILE)</UserProfileHome>
-    <!--$(UsingMicrosoftNETSdk)-->
-    <SponsorLinkSupported>true</SponsorLinkSupported>
-    <SponsorLinkHome Condition="$(SponsorLinkSupported) == 'true'">$([System.IO.Path]::GetFullPath('$(UserProfileHome)/.sponsorlink'))</SponsorLinkHome>
-  </PropertyGroup>
-
-  <ItemGroup Condition="'$(SponsorLinkHome)' != ''">
-    <SponsorManifest Include="$(SponsorLinkHome)/github/*.jwt" />
-    <AdditionalFiles Include="@(SponsorManifest -> Distinct())" Kind="SponsorLink" />
-
-    <CompilerVisibleProperty Include="DesignTimeBuild" />
-
-    <CompilerVisibleItemMetadata Include="AdditionalFiles" MetadataName="Kind" />
-  </ItemGroup>
-
-  <Target Name="CleanExpiredAutoSyncStamp" Condition="Exists($(AutoSyncStampFile))" BeforeTargets="AutoSyncSponsors">
-
-  </Target>
-
-  <Target Name="AutoSyncSponsors" Condition="!Exists($(AutoSyncStampFile))" BeforeTargets="GenerateMSBuildEditorConfigFileShouldRun">
-    <AutoSyncManifest />
-    <WriteLinesToFile File="$(AutoSyncStampFile)" Lines='$([System.DateTime]::Now.ToString("yyyy-MM-yy"))' Overwrite="true" />
-  </Target>
-
-  <Target Name="SL_GitConfig"
-          Condition="'$(SourceControlInformationFeatureSupported)' == 'true'"
-          BeforeTargets="GenerateMSBuildEditorConfigFileShouldRun"
-          DependsOnTargets="InitializeSourceControlInformation">
-    <ItemGroup>
-      <GitRoot Include="@(SourceRoot -> WithMetadataValue('SourceControl', 'git'))" />
-    </ItemGroup>
-    <PropertyGroup>
-      <GitRoot>%(GitRoot.FullPath)</GitRoot>
-    </PropertyGroup>
-    <ItemGroup Condition="'$(GitRoot)' != ''">
-      <AdditionalFiles Include="$([System.IO.Path]::GetFullPath($(GitRoot).git/config))" Kind="GitConfig" />
-      <AdditionalFiles Include="$([System.IO.Path]::GetFullPath($(UserProfileHome)/.gitconfig))" Kind="GitConfig" />
-    </ItemGroup>
-  </Target>
-
-</Project>
\ No newline at end of file
diff --git a/src/SponsorLink/SponsorLink/ThisAssembly.cs b/src/SponsorLink/SponsorLink/ThisAssembly.cs
deleted file mode 100644
index 89f2316..0000000
--- a/src/SponsorLink/SponsorLink/ThisAssembly.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-// <autogenerated />
-partial class ThisAssembly
-{
-    partial class Strings
-    {
-        partial class Unknown
-        {
-            public static string MessageFormat => GetResourceManager("Devlooped.SponsorLink").GetString("Unknown_Message");
-        }
-
-        partial class Expiring
-        {
-            public static string MessageFormat => GetResourceManager("Devlooped.SponsorLink").GetString("Expiring_Message");
-        }
-
-        partial class Expired
-        {
-            public static string MessageFormat => GetResourceManager("Devlooped.SponsorLink").GetString("Expired_Message");
-        }
-
-        partial class Grace
-        {
-            public static string MessageFormat => GetResourceManager("Devlooped.SponsorLink").GetString("Grace_Message");
-        }
-
-        partial class Sponsor
-        {
-            public static string MessageFormat => GetResourceManager("Devlooped.SponsorLink").GetString("Sponsor_Message");
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/SponsorLink/SponsorLink/Tracing.cs b/src/SponsorLink/SponsorLink/Tracing.cs
deleted file mode 100644
index 9201796..0000000
--- a/src/SponsorLink/SponsorLink/Tracing.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-// <autogenerated />
-#nullable enable
-using System;
-using System.Diagnostics;
-using System.IO;
-using System.Runtime.CompilerServices;
-using System.Text;
-
-namespace Devlooped.Sponsors;
-
-static class Tracing
-{
-    public static void Trace(string message, object? value, [CallerArgumentExpression("value")] string? expression = null, [CallerFilePath] string? filePath = null, [CallerLineNumber] int lineNumber = 0)
-        => Trace($"{message}: {value} ({expression})", filePath, lineNumber);
-
-    public static void Trace(object? value, [CallerArgumentExpression("value")] string? expression = null, [CallerFilePath] string? filePath = null, [CallerLineNumber] int lineNumber = 0)
-        => Trace($"{value} ({expression})", filePath, lineNumber);
-
-    public static void Trace([CallerMemberName] string? message = null, [CallerFilePath] string? filePath = null, [CallerLineNumber] int lineNumber = 0)
-    {
-        var trace = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("SPONSORLINK_TRACE"));
-#if DEBUG
-        trace = true;
-#endif
-
-        if (!trace)
-            return;
-
-        var line = new StringBuilder()
-            .Append($"[{DateTime.Now:O}]")
-            .Append($"[{Process.GetCurrentProcess().ProcessName}:{Process.GetCurrentProcess().Id}]")
-            .Append($" {message} ")
-            .AppendLine($" -> {filePath}({lineNumber})")
-            .ToString();
-
-        var dir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData, Environment.SpecialFolderOption.Create);
-        var tries = 0;
-        // Best-effort only
-        while (tries < 10)
-        {
-            try
-            {
-                File.AppendAllText(Path.Combine(dir, "SponsorLink.log"), line);
-                Debugger.Log(0, "SponsorLink", line);
-                return;
-            }
-            catch (IOException) 
-            { 
-                tries++; 
-            }
-        }
-    }
-}
diff --git a/src/SponsorLink/SponsorLink/buildTransitive/Devlooped.Sponsors.targets b/src/SponsorLink/SponsorLink/buildTransitive/Devlooped.Sponsors.targets
deleted file mode 100644
index 471f37f..0000000
--- a/src/SponsorLink/SponsorLink/buildTransitive/Devlooped.Sponsors.targets
+++ /dev/null
@@ -1,99 +0,0 @@
-<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-
-  <PropertyGroup>
-    <!-- We autosync manifests daily, if opted-in -->
-    <Today>$([System.DateTime]::Now.ToString("yyyy-MM-yy"))</Today>
-    <!-- Daily sync stamp, if opted-in -->
-    <SyncStampFile>$(BaseIntermediateOutputPath)autosync-$(Today).stamp</SyncStampFile>
-    <!-- Incremental read of autosync setting -->
-    <AutoSyncStampFile>$(BaseIntermediateOutputPath)autosync.stamp</AutoSyncStampFile>
-
-    <UserProfileHome Condition="'$([MSBuild]::IsOSUnixLike())' == 'true'">$(HOME)</UserProfileHome>
-    <UserProfileHome Condition="'$([MSBuild]::IsOSUnixLike())' != 'true'">$(USERPROFILE)</UserProfileHome>
-    <!-- Root dir for SL files -->
-    <SponsorLinkHome>$([System.IO.Path]::GetFullPath('$(UserProfileHome)/.sponsorlink'))</SponsorLinkHome>
-    <!-- SL dotnet-config file -->
-    <SponsorLinkConfig>$([System.IO.Path]::Combine('$(SponsorLinkHome)', '.netconfig'))</SponsorLinkConfig>
-  </PropertyGroup>
-
-  <ItemGroup>
-    <!-- All GH manifests. For now, we don't support other funding platforms -->
-    <SponsorManifest Include="$(SponsorLinkHome)/github/*.jwt" />
-
-    <CompilerVisibleItemMetadata Include="AdditionalFiles" MetadataName="SourceItemType" />
-    <!-- To quickly exit if true -->
-    <CompilerVisibleProperty Include="DesignTimeBuild" />
-  </ItemGroup>
-
-  <PropertyGroup>
-    <SLDependsOn>SL_CollectDependencies</SLDependsOn>
-    <SLDependsOn Condition="'$(BuildingInsideVisualStudio)' == 'true' and '$(DesignTimeBuild)' != 'true'">$(SLDependsOn);SL_CheckAutoSync;SL_ReadAutoSyncEnabled;SL_SyncSponsors</SLDependsOn>
-  </PropertyGroup>
-
-  <Target Name="SL" BeforeTargets="GenerateMSBuildEditorConfigFileShouldRun" DependsOnTargets="$(SLDependsOn)">
-    <ItemGroup>
-      <!-- Make manifests visible to analyzers -->
-      <AdditionalFiles Include="@(SponsorManifest -> Distinct())" SourceItemType="SponsorManifest" />
-    </ItemGroup>
-  </Target>
-
-  <Target Name="SL_CollectDependencies" DependsOnTargets="_GenerateRestoreGraph">
-    <!-- Makes direct dependencies visible to the compiler, so that sponsoring checks can selectively skip indirectly referenced analyzer -->
-    <ItemGroup>
-      <CompilerVisibleProperty Include="$([MSBuild]::ValueOrDefault('%(_RestoreGraphEntry.Id)', '').Replace('.', '_'))" />
-    </ItemGroup>
-    <CreateProperty Value="%(_RestoreGraphEntry.VersionRange)" Condition="%(_RestoreGraphEntry.Type) == 'Dependency'" >
-      <Output TaskParameter="Value" PropertyName="$([MSBuild]::ValueOrDefault('%(_RestoreGraphEntry.Id)', '').Replace('.', '_'))"/>
-    </CreateProperty>
-  </Target>
-
-  <Target Name="SL_Clean" AfterTargets="Clean">
-    <!-- Cleanup of stamp files -->
-    <ItemGroup>
-      <AutoSyncStampFile Include="$(BaseIntermediateOutputPath)autosync*.stamp" />
-    </ItemGroup>
-    <Delete Files="@(AutoSyncStampFile)" />
-  </Target>
-  
-  <Target Name="SL_CheckAutoSync" Inputs="$(SponsorLinkConfig)" Outputs="$(AutoSyncStampFile)" Condition="Exists($(SponsorLinkConfig))">
-    <!-- Read autosync setting from config file and cache its value -->
-    <ReadLinesFromFile File="$(SponsorLinkConfig)">
-      <Output TaskParameter="Lines" ItemName="SLConfig" />
-    </ReadLinesFromFile>
-    <ItemGroup>
-      <SLConfigAutoSync Include="$([MSBuild]::ValueOrDefault(%(SLConfig.Identity), '').Trim())"
-                        Condition="$([MSBuild]::ValueOrDefault(%(SLConfig.Identity), '').Trim().StartsWith('autosync'))" />
-    </ItemGroup>
-    <PropertyGroup>
-      <SLConfigAutoSync>%(SLConfigAutoSync.Identity)</SLConfigAutoSync>
-      <AutoSyncEnabled Condition="$(SLConfigAutoSync.EndsWith('true'))">true</AutoSyncEnabled>
-      <AutoSyncEnabled Condition="$(SLConfigAutoSync.EndsWith('false'))">false</AutoSyncEnabled>
-    </PropertyGroup>
-    <WriteLinesToFile File="$(AutoSyncStampFile)" Lines="$(AutoSyncEnabled)" Overwrite="true" />
-  </Target>
-
-  <Target Name="SL_ReadAutoSyncEnabled" Condition="'$(AutoSyncEnabled)' == '' and Exists($(AutoSyncStampFile))">
-    <!-- In subsequent runs, directly read the cached value from the previous target -->
-    <PropertyGroup>
-      <AutoSyncEnabled>$([System.IO.File]::ReadAllText($(AutoSyncStampFile)).Trim())</AutoSyncEnabled>
-    </PropertyGroup>
-  </Target>
-
-  <Target Name="SL_SyncSponsors" Inputs="@(SponsorLinkManifest);$(SponsorLinkConfig)" Outputs="$(SyncStampFile)" Condition="'$(AutoSyncEnabled)' == 'true'">
-    <!-- Sync all local manifests in unattended mode (non-interactive).
-         It's possible that some manifests will need interactive sync, and we'll render the 
-         messages in that case. 
-         Note that since running this requires autosync=true, we can safely assume the user 
-         has already run `gh sponsors [...] -autosync` at least once to turn it on. Otherwise, 
-         this target won't run at all.
-         -->
-    <Exec Command="gh sponsors sync --local --unattended" StandardErrorImportance="high" StandardOutputImportance="low" ConsoleToMSBuild="true" ContinueOnError="true">
-      <Output TaskParameter="ExitCode" PropertyName="SponsorsExitCode" />
-      <Output TaskParameter="ConsoleOutput" PropertyName="SponsorsOutput" />
-    </Exec>
-    <Message Importance="high" Text="$(SponsorsOutput)" Condition="'$(SponsorsExitCode)' != '0'" />
-    <!-- We only touch the stamp file to avoid future sync runs if we could sync all manifests  -->
-    <Touch Files="$(SyncStampFile)" Condition="$(SponsorsExitCode) == '0'" AlwaysCreate="True" />
-  </Target>
-
-</Project>
\ No newline at end of file
diff --git a/src/SponsorLink/SponsorLink/devlooped.pub.jwk b/src/SponsorLink/SponsorLink/devlooped.pub.jwk
deleted file mode 100644
index cdf45c2..0000000
--- a/src/SponsorLink/SponsorLink/devlooped.pub.jwk
+++ /dev/null
@@ -1,5 +0,0 @@
-{
-  "e": "AQAB",
-  "kty": "RSA",
-  "n": "5inhv8QymaDBOihNi1eY-6-hcIB5qSONFZxbxxXAyOtxAdjFCPM-94gIZqM9CDrX3pyg1lTJfml_a_FZSU9dB1ii5mSX_mNHBFXn1_l_gi1ErdbkIF5YbW6oxWFxf3G5mwVXwnPfxHTyQdmWQ3YJR-A3EB4kaFwLqA6Ha5lb2ObGpMTQJNakD4oTAGDhqHMGhu6PupGq5ie4qZcQ7N8ANw8xH7nicTkbqEhQABHWOTmLBWq5f5F6RYGF8P7cl0IWl_w4YcIZkGm2vX2fi26F9F60cU1v13GZEVDTXpJ9kzvYeM9sYk6fWaoyY2jhE51qbv0B0u6hScZiLREtm3n7ClJbIGXhkUppFS2JlNaX3rgQ6t-4LK8gUTyLt3zDs2H8OZyCwlCpfmGmdsUMkm1xX6t2r-95U3zywynxoWZfjBCJf41leM9OMKYwNWZ6LQMyo83HWw1PBIrX4ZLClFwqBcSYsXDyT8_ZLd1cdYmPfmtllIXxZhLClwT5qbCWv73V"
-}
\ No newline at end of file
diff --git a/src/SponsorLink/SponsorLink/sponsorable.md b/src/SponsorLink/SponsorLink/sponsorable.md
deleted file mode 100644
index c023c25..0000000
--- a/src/SponsorLink/SponsorLink/sponsorable.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Why Sponsor
-
-Well, why not? It's super cheap :)
-
-This could even be partially auto-generated from FUNDING.yml and what-not.
\ No newline at end of file
diff --git a/src/SponsorLink/SponsorLinkAnalyzer.sln b/src/SponsorLink/SponsorLinkAnalyzer.sln
deleted file mode 100644
index be206b1..0000000
--- a/src/SponsorLink/SponsorLinkAnalyzer.sln
+++ /dev/null
@@ -1,43 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.10.34928.147
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Analyzer", "Analyzer\Analyzer.csproj", "{584984D6-926B-423D-9416-519613423BAE}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Library", "Library\Library.csproj", "{598CD398-A172-492C-8367-827D43276029}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests\Tests.csproj", "{EA02494C-6ED4-47A0-8D43-20F50BE8554F}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SponsorLink", "SponsorLink\SponsorLink.csproj", "{B91C7E99-3D2E-4FDF-B017-9123E810197F}"
-EndProject
-Global
-	GlobalSection(SolutionConfigurationPlatforms) = preSolution
-		Debug|Any CPU = Debug|Any CPU
-		Release|Any CPU = Release|Any CPU
-	EndGlobalSection
-	GlobalSection(ProjectConfigurationPlatforms) = postSolution
-		{584984D6-926B-423D-9416-519613423BAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{584984D6-926B-423D-9416-519613423BAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{584984D6-926B-423D-9416-519613423BAE}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{584984D6-926B-423D-9416-519613423BAE}.Release|Any CPU.Build.0 = Release|Any CPU
-		{598CD398-A172-492C-8367-827D43276029}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{598CD398-A172-492C-8367-827D43276029}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{598CD398-A172-492C-8367-827D43276029}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{598CD398-A172-492C-8367-827D43276029}.Release|Any CPU.Build.0 = Release|Any CPU
-		{EA02494C-6ED4-47A0-8D43-20F50BE8554F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{EA02494C-6ED4-47A0-8D43-20F50BE8554F}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{EA02494C-6ED4-47A0-8D43-20F50BE8554F}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{EA02494C-6ED4-47A0-8D43-20F50BE8554F}.Release|Any CPU.Build.0 = Release|Any CPU
-		{B91C7E99-3D2E-4FDF-B017-9123E810197F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{B91C7E99-3D2E-4FDF-B017-9123E810197F}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{B91C7E99-3D2E-4FDF-B017-9123E810197F}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{B91C7E99-3D2E-4FDF-B017-9123E810197F}.Release|Any CPU.Build.0 = Release|Any CPU
-	EndGlobalSection
-	GlobalSection(SolutionProperties) = preSolution
-		HideSolutionNode = FALSE
-	EndGlobalSection
-	GlobalSection(ExtensibilityGlobals) = postSolution
-		SolutionGuid = {1DDA0EFF-BEF6-49BB-8AA8-D71FE1CD3E6F}
-	EndGlobalSection
-EndGlobal
diff --git a/src/SponsorLink/Tests/.netconfig b/src/SponsorLink/Tests/.netconfig
deleted file mode 100644
index 3b3bd0d..0000000
--- a/src/SponsorLink/Tests/.netconfig
+++ /dev/null
@@ -1,15 +0,0 @@
-[file "SponsorableManifest.cs"]
-	url = https://github.com/devlooped/SponsorLink/blob/main/src/Core/SponsorableManifest.cs
-	sha = 976ecefc44d87217e04933d9cd7f6b950468410b
-	etag = e0c95e7fc6c0499dbc8c5cd28aa9a6a5a49c9d0ad41fe028a5a085aca7e00eaf
-	weak
-[file "JsonOptions.cs"]
-	url = https://github.com/devlooped/SponsorLink/blob/main/src/Core/JsonOptions.cs
-	sha = 79dc56ce45fc36df49e1c4f8875e93c297edc383
-	etag = 6e9a1b12757a97491441b9534ced4e5dac6d9d6334008fa0cd20575650bbd935
-	weak
-[file "Extensions.cs"]
-	url = https://github.com/devlooped/SponsorLink/blob/main/src/Core/Extensions.cs
-	sha = d204b667eace818934c49e09b5b08ea82aef87fa
-	etag = f68e11894103f8748ce290c29927bf1e4f749e743ae33d5350e72ed22c15d245
-	weak
diff --git a/src/SponsorLink/Tests/Attributes.cs b/src/SponsorLink/Tests/Attributes.cs
deleted file mode 100644
index aa5f48d..0000000
--- a/src/SponsorLink/Tests/Attributes.cs
+++ /dev/null
@@ -1,59 +0,0 @@
-using Microsoft.Extensions.Configuration;
-using Xunit;
-
-public class SecretsFactAttribute : FactAttribute
-{
-    public SecretsFactAttribute(params string[] secrets)
-    {
-        var configuration = new ConfigurationBuilder()
-            .AddUserSecrets<SecretsFactAttribute>()
-            .Build();
-
-        var missing = new HashSet<string>();
-
-        foreach (var secret in secrets)
-        {
-            if (string.IsNullOrEmpty(configuration[secret]))
-                missing.Add(secret);
-        }
-
-        if (missing.Count > 0)
-            Skip = "Missing user secrets: " + string.Join(',', missing);
-    }
-}
-
-public class LocalFactAttribute : SecretsFactAttribute
-{
-    public LocalFactAttribute(params string[] secrets) : base(secrets)
-    {
-        if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CI")))
-            Skip = "Non-CI test";
-    }
-}
-
-public class CIFactAttribute : FactAttribute
-{
-    public CIFactAttribute()
-    {
-        if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CI")))
-            Skip = "CI-only test";
-    }
-}
-
-public class LocalTheoryAttribute : TheoryAttribute
-{
-    public LocalTheoryAttribute()
-    {
-        if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CI")))
-            Skip = "Non-CI test";
-    }
-}
-
-public class CITheoryAttribute : TheoryAttribute
-{
-    public CITheoryAttribute()
-    {
-        if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CI")))
-            Skip = "CI-only test";
-    }
-}
\ No newline at end of file
diff --git a/src/SponsorLink/Tests/Extensions.cs b/src/SponsorLink/Tests/Extensions.cs
deleted file mode 100644
index 75a78b4..0000000
--- a/src/SponsorLink/Tests/Extensions.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-using System.Diagnostics.CodeAnalysis;
-using System.Runtime.CompilerServices;
-using Microsoft.Extensions.Logging;
-
-namespace Devlooped.Sponsors;
-
-static class Extensions
-{
-    public static HashCode Add(this HashCode hash, params object[] items)
-    {
-        foreach (var item in items)
-            hash.Add(item);
-
-        return hash;
-    }
-
-
-    public static HashCode AddRange<T>(this HashCode hash, IEnumerable<T> items)
-    {
-        foreach (var item in items)
-            hash.Add(item);
-
-        return hash;
-    }
-
-    public static Array Cast(this Array array, Type elementType)
-    {
-        //Convert the object list to the destination array type.
-        var result = Array.CreateInstance(elementType, array.Length);
-        Array.Copy(array, result, array.Length);
-        return result;
-    }
-
-    public static void Assert(this ILogger logger, [DoesNotReturnIf(false)] bool condition, [CallerArgumentExpression(nameof(condition))] string? message = default, params object?[] args)
-    {
-        if (!condition)
-        {
-            //Debug.Assert(condition, message);
-            logger.LogError(message, args);
-            throw new InvalidOperationException(message);
-        }
-    }
-}
diff --git a/src/SponsorLink/Tests/JsonOptions.cs b/src/SponsorLink/Tests/JsonOptions.cs
deleted file mode 100644
index c816eba..0000000
--- a/src/SponsorLink/Tests/JsonOptions.cs
+++ /dev/null
@@ -1,72 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-using System.Text.Json.Serialization.Metadata;
-using Microsoft.IdentityModel.Tokens;
-
-namespace Devlooped.Sponsors;
-
-static partial class JsonOptions
-{
-    public static JsonSerializerOptions Default { get; } =
-#if NET6_0_OR_GREATER
-        new(JsonSerializerDefaults.Web)
-#else
-        new()
-#endif
-        {
-            AllowTrailingCommas = true,
-            PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
-            ReadCommentHandling = JsonCommentHandling.Skip,
-#if NET6_0_OR_GREATER
-            DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault | JsonIgnoreCondition.WhenWritingNull,
-#endif
-            WriteIndented = true,
-            Converters =
-            {
-                new JsonStringEnumConverter(allowIntegerValues: false),
-#if NET6_0_OR_GREATER
-                new DateOnlyJsonConverter()
-#endif
-            }
-        };
-
-    public static JsonSerializerOptions JsonWebKey { get; } = new(JsonSerializerOptions.Default)
-    {
-        WriteIndented = true,
-        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault | JsonIgnoreCondition.WhenWritingNull,
-        TypeInfoResolver = new DefaultJsonTypeInfoResolver
-        {
-            Modifiers =
-                {
-                    info =>
-                    {
-                        if (info.Type != typeof(JsonWebKey))
-                            return;
-
-                        foreach (var prop in info.Properties)
-                        {
-                            // Don't serialize empty lists, makes for more concise JWKs
-                            prop.ShouldSerialize = (obj, value) =>
-                                value is not null &&
-                                (value is not IList<string> list || list.Count > 0);
-                        }
-                    }
-                }
-        }
-    };
-
-
-#if NET6_0_OR_GREATER
-    public class DateOnlyJsonConverter : JsonConverter<DateOnly>
-    {
-        public override DateOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
-            => DateOnly.Parse(reader.GetString()?[..10] ?? "", CultureInfo.InvariantCulture);
-
-        public override void Write(Utf8JsonWriter writer, DateOnly value, JsonSerializerOptions options)
-            => writer.WriteStringValue(value.ToString("O", CultureInfo.InvariantCulture));
-    }
-#endif
-}
diff --git a/src/SponsorLink/Tests/Sample.cs b/src/SponsorLink/Tests/Sample.cs
deleted file mode 100644
index 6249e62..0000000
--- a/src/SponsorLink/Tests/Sample.cs
+++ /dev/null
@@ -1,59 +0,0 @@
-extern alias Analyzer;
-using System;
-using System.Globalization;
-using System.Runtime.CompilerServices;
-using System.Security.Cryptography;
-using Analyzer::Devlooped.Sponsors;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace Tests;
-
-public class Sample(ITestOutputHelper output)
-{
-    [Theory]
-    [InlineData("es-AR", SponsorStatus.Unknown)]
-    [InlineData("es-AR", SponsorStatus.Expiring)]
-    [InlineData("es-AR", SponsorStatus.Expired)]
-    [InlineData("es-AR", SponsorStatus.Sponsor)]
-    [InlineData("en", SponsorStatus.Unknown)]
-    [InlineData("en", SponsorStatus.Expiring)]
-    [InlineData("en", SponsorStatus.Expired)]
-    [InlineData("en", SponsorStatus.Sponsor)]
-    [InlineData("", SponsorStatus.Unknown)]
-    [InlineData("", SponsorStatus.Expiring)]
-    [InlineData("", SponsorStatus.Expired)]
-    [InlineData("", SponsorStatus.Sponsor)]
-    public void Test(string culture, SponsorStatus kind)
-    {
-        Thread.CurrentThread.CurrentCulture = Thread.CurrentThread.CurrentUICulture =
-            culture == "" ? CultureInfo.InvariantCulture : CultureInfo.GetCultureInfo(culture);
-
-        var diag = new DiagnosticsManager().GetDescriptor(["foo"], "bar", "FB", kind);
-
-        output.WriteLine(diag.Title.ToString());
-        output.WriteLine(diag.MessageFormat.ToString());
-        output.WriteLine(diag.Description.ToString());
-    }
-
-    [Fact]
-    public void RenderSponsorables()
-    {
-        Assert.NotEmpty(SponsorLink.Sponsorables);
-
-        foreach (var pair in SponsorLink.Sponsorables)
-        {
-            output.WriteLine($"{pair.Key} = {pair.Value}");
-            // Read the JWK
-            var jsonWebKey = Microsoft.IdentityModel.Tokens.JsonWebKey.Create(pair.Value);
-
-            Assert.NotNull(jsonWebKey);
-
-            using var key = RSA.Create(new RSAParameters
-            {
-                Modulus = Microsoft.IdentityModel.Tokens.Base64UrlEncoder.DecodeBytes(jsonWebKey.N),
-                Exponent = Microsoft.IdentityModel.Tokens.Base64UrlEncoder.DecodeBytes(jsonWebKey.E),
-            });
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/SponsorLink/Tests/SponsorLinkTests.cs b/src/SponsorLink/Tests/SponsorLinkTests.cs
deleted file mode 100644
index 7625e2c..0000000
--- a/src/SponsorLink/Tests/SponsorLinkTests.cs
+++ /dev/null
@@ -1,126 +0,0 @@
-extern alias Analyzer;
-using System.Security.Cryptography;
-using System.Text.Json;
-using Analyzer::Devlooped.Sponsors;
-using Devlooped.Sponsors;
-using Microsoft.IdentityModel.Tokens;
-using Xunit;
-
-namespace Devlooped.Tests;
-
-public class SponsorLinkTests
-{
-    // We need to convert to jwk string since the analyzer project has merged the JWT assembly and types.
-    public static string ToJwk(SecurityKey key)
-        => JsonSerializer.Serialize(
-            JsonWebKeyConverter.ConvertFromSecurityKey(key),
-            JsonOptions.JsonWebKey);
-
-    [Fact]
-    public void ValidateSponsorable()
-    {
-        var manifest = SponsorableManifest.Create(new Uri("https://foo.com"), [new Uri("https://github.com/sponsors/bar")], "ASDF1234");
-        var jwt = manifest.ToJwt();
-        var jwk = ToJwk(manifest.SecurityKey);
-
-        // NOTE: sponsorable manifest doesn't have expiration date.
-        var status = SponsorLink.Validate(jwt, jwk, out var token, out var principal, false);
-
-        Assert.Equal(ManifestStatus.Valid, status);
-    }
-
-    [Fact]
-    public void ValidateWrongKey()
-    {
-        var manifest = SponsorableManifest.Create(new Uri("https://foo.com"), [new Uri("https://github.com/sponsors/bar")], "ASDF1234");
-        var jwt = manifest.ToJwt();
-        var jwk = ToJwk(new RsaSecurityKey(RSA.Create()));
-
-        var status = SponsorLink.Validate(jwt, jwk, out var token, out var principal, false);
-
-        Assert.Equal(ManifestStatus.Invalid, status);
-
-        // We should still be a able to read the data, knowing it may have been tampered with.
-        Assert.NotNull(principal);
-        Assert.NotNull(token);
-    }
-
-    [Fact]
-    public void ValidateExpiredSponsor()
-    {
-        var manifest = SponsorableManifest.Create(new Uri("https://foo.com"), [new Uri("https://github.com/sponsors/bar")], "ASDF1234");
-        var jwk = ToJwk(manifest.SecurityKey);
-        var sponsor = manifest.Sign([], expiration: TimeSpan.Zero);
-
-        // Will be expired after this.
-        Thread.Sleep(1000);
-
-        var status = SponsorLink.Validate(sponsor, jwk, out var token, out var principal, true);
-
-        Assert.Equal(ManifestStatus.Expired, status);
-
-        // We should still be a able to read the data, even if expired (but not tampered with).
-        Assert.NotNull(principal);
-        Assert.NotNull(token);
-    }
-
-    [Fact]
-    public void ValidateUnknownFormat()
-    {
-        var manifest = SponsorableManifest.Create(new Uri("https://foo.com"), [new Uri("https://github.com/sponsors/bar")], "ASDF1234");
-        var jwk = ToJwk(manifest.SecurityKey);
-
-        var status = SponsorLink.Validate("asdfasdf", jwk, out var token, out var principal, false);
-
-        Assert.Equal(ManifestStatus.Unknown, status);
-
-        // Nothing could be read at all.
-        Assert.Null(principal);
-        Assert.Null(token);
-    }
-
-    [Fact]
-    public void TryRead()
-    {
-        var fooSponsorable = SponsorableManifest.Create(new Uri("https://foo.com"), [new Uri("https://github.com/sponsors/foo")], "ASDF1234");
-        var barSponsorable = SponsorableManifest.Create(new Uri("https://bar.com"), [new Uri("https://github.com/sponsors/bar")], "GHJK5678");
-
-        // Org sponsor and member of team
-        var fooSponsor = fooSponsorable.Sign([new("sub", "kzu"), new("email", "me@foo.com"), new("roles", "org"), new("roles", "team")], expiration: TimeSpan.FromDays(30));
-        // Org + personal sponsor
-        var barSponsor = barSponsorable.Sign([new("sub", "kzu"), new("email", "me@bar.com"), new("roles", "org"), new("roles", "user")], expiration: TimeSpan.FromDays(30));
-
-        Assert.True(SponsorLink.TryRead(out var principal, [(fooSponsor, ToJwk(fooSponsorable.SecurityKey)), (barSponsor, ToJwk(barSponsorable.SecurityKey))]));
-
-        // Can check role across both JWTs
-        Assert.True(principal.IsInRole("org"));
-        Assert.True(principal.IsInRole("team"));
-        Assert.True(principal.IsInRole("user"));
-
-        Assert.True(principal.HasClaim("sub", "kzu"));
-        Assert.True(principal.HasClaim("email", "me@foo.com"));
-        Assert.True(principal.HasClaim("email", "me@bar.com"));
-    }
-
-    [LocalFact]
-    public void ValidateCachedManifest()
-    {
-        var path = Environment.ExpandEnvironmentVariables("%userprofile%\\.sponsorlink\\github\\devlooped.jwt");
-        if (!File.Exists(path))
-            return;
-
-        var jwt = File.ReadAllText(path);
-
-        var status = SponsorLink.Validate(jwt,
-            """
-            {
-              "e": "AQAB",
-              "kty": "RSA",
-              "n": "5inhv8QymaDBOihNi1eY-6-hcIB5qSONFZxbxxXAyOtxAdjFCPM-94gIZqM9CDrX3pyg1lTJfml_a_FZSU9dB1ii5mSX_mNHBFXn1_l_gi1ErdbkIF5YbW6oxWFxf3G5mwVXwnPfxHTyQdmWQ3YJR-A3EB4kaFwLqA6Ha5lb2ObGpMTQJNakD4oTAGDhqHMGhu6PupGq5ie4qZcQ7N8ANw8xH7nicTkbqEhQABHWOTmLBWq5f5F6RYGF8P7cl0IWl_w4YcIZkGm2vX2fi26F9F60cU1v13GZEVDTXpJ9kzvYeM9sYk6fWaoyY2jhE51qbv0B0u6hScZiLREtm3n7ClJbIGXhkUppFS2JlNaX3rgQ6t-4LK8gUTyLt3zDs2H8OZyCwlCpfmGmdsUMkm1xX6t2r-95U3zywynxoWZfjBCJf41leM9OMKYwNWZ6LQMyo83HWw1PBIrX4ZLClFwqBcSYsXDyT8_ZLd1cdYmPfmtllIXxZhLClwT5qbCWv73V"
-            }
-            """
-            , out var token, out var principal, false);
-
-        Assert.Equal(ManifestStatus.Valid, status);
-    }
-}
diff --git a/src/SponsorLink/Tests/SponsorableManifest.cs b/src/SponsorLink/Tests/SponsorableManifest.cs
deleted file mode 100644
index 5ae6e3f..0000000
--- a/src/SponsorLink/Tests/SponsorableManifest.cs
+++ /dev/null
@@ -1,309 +0,0 @@
-using System.Diagnostics.CodeAnalysis;
-using System.IdentityModel.Tokens.Jwt;
-using System.Security.Claims;
-using System.Security.Cryptography;
-using System.Text.Json;
-using Microsoft.IdentityModel.Tokens;
-
-namespace Devlooped.Sponsors;
-
-/// <summary>
-/// The serializable manifest of a sponsorable user, as persisted 
-/// in the .github/sponsorlink.jwt file.
-/// </summary>
-public class SponsorableManifest
-{
-    /// <summary>
-    /// Overall manifest status.
-    /// </summary>
-    public enum Status
-    {
-        /// <summary>
-        /// SponsorLink manifest is invalid.
-        /// </summary>
-        Invalid,
-        /// <summary>
-        /// The manifest has an audience that doesn't match the sponsorable account.
-        /// </summary>
-        AccountMismatch,
-        /// <summary>
-        /// SponsorLink manifest not found for the given account, so it's not supported.
-        /// </summary>
-        NotFound,
-        /// <summary>
-        /// Manifest was successfully fetched and validated.
-        /// </summary>
-        OK,
-    }
-
-    /// <summary>
-    /// Creates a new manifest with a new RSA key pair.
-    /// </summary>
-    public static SponsorableManifest Create(Uri issuer, Uri[] audience, string clientId)
-    {
-        var rsa = RSA.Create(3072);
-        var pub = Convert.ToBase64String(rsa.ExportRSAPublicKey());
-
-        return new SponsorableManifest(issuer, audience, clientId, new RsaSecurityKey(rsa), pub);
-    }
-
-    public static async Task<(Status, SponsorableManifest?)> FetchAsync(string sponsorable, string? branch, HttpClient? http = default)
-    {
-        // Try to detect sponsorlink manifest in the sponsorable .github repo
-        var url = $"https://github.com/{sponsorable}/.github/raw/{branch ?? "main"}/sponsorlink.jwt";
-
-        // Manifest should be public, so no need for any special HTTP client.
-        using (http ??= new HttpClient())
-        {
-            var response = await http.GetAsync(url);
-            if (!response.IsSuccessStatusCode)
-                return (Status.NotFound, default);
-
-            var jwt = await response.Content.ReadAsStringAsync();
-            if (!TryRead(jwt, out var manifest, out var missingClaim))
-                return (Status.Invalid, default);
-
-            // Manifest audience should match the sponsorable account to avoid weird issues?
-            if (sponsorable != manifest.Sponsorable)
-                return (Status.AccountMismatch, default);
-
-            return (Status.OK, manifest);
-        }
-    }
-
-    /// <summary>
-    /// Parses a JWT into a <see cref="SponsorableManifest"/>.
-    /// </summary>
-    /// <param name="jwt">The JWT containing the sponsorable information.</param>
-    /// <param name="manifest">The parsed manifest, if not required claims are missing.</param>
-    /// <param name="missingClaim">The missing required claim, if any.</param>
-    /// <returns>A validated manifest.</returns>
-    public static bool TryRead(string jwt, [NotNullWhen(true)] out SponsorableManifest? manifest, out string? missingClaim)
-    {
-        var handler = new JwtSecurityTokenHandler { MapInboundClaims = false };
-        missingClaim = null;
-        manifest = default;
-
-        if (!handler.CanReadToken(jwt))
-            return false;
-
-        var token = handler.ReadJwtToken(jwt);
-        var issuer = token.Issuer;
-
-        if (token.Audiences.FirstOrDefault(x => x.StartsWith("https://github.com/")) is null)
-        {
-            missingClaim = "aud";
-            return false;
-        }
-
-        if (token.Claims.FirstOrDefault(c => c.Type == "client_id")?.Value is not string clientId)
-        {
-            missingClaim = "client_id";
-            return false;
-        }
-
-        if (token.Claims.FirstOrDefault(c => c.Type == "pub")?.Value is not string pub)
-        {
-            missingClaim = "pub";
-            return false;
-        }
-
-        if (token.Claims.FirstOrDefault(c => c.Type == "sub_jwk")?.Value is not string jwk)
-        {
-            missingClaim = "sub_jwk";
-            return false;
-        }
-
-        var key = new JsonWebKeySet { Keys = { JsonWebKey.Create(jwk) } }.GetSigningKeys().First();
-        manifest = new SponsorableManifest(new Uri(issuer), token.Audiences.Select(x => new Uri(x)).ToArray(), clientId, key, pub);
-
-        return true;
-    }
-
-    public SponsorableManifest(Uri issuer, Uri[] audience, string clientId, SecurityKey publicKey, string publicRsaKey)
-    {
-        Issuer = issuer.AbsoluteUri;
-        Audience = audience.Select(a => a.AbsoluteUri.TrimEnd('/')).ToArray();
-        ClientId = clientId;
-        SecurityKey = publicKey;
-        PublicKey = publicRsaKey;
-        Sponsorable = audience.Where(x => x.Host == "github.com").Select(x => x.Segments.LastOrDefault()?.TrimEnd('/')).FirstOrDefault() ??
-            throw new ArgumentException("At least one of the intended audience must be a GitHub sponsors URL.");
-    }
-
-    /// <summary>
-    /// Converts (and optionally signs) the manifest into a JWT. Never exports the private key.
-    /// </summary>
-    /// <param name="signing">Optional credentials when signing the resulting manifest. Defaults to the <see cref="SecurityKey"/> if it has a private key.</param>
-    /// <returns>The JWT manifest.</returns>
-    public string ToJwt(SigningCredentials? signing = default)
-    {
-        var jwk = JsonWebKeyConverter.ConvertFromSecurityKey(SecurityKey);
-
-        // Automatically sign if the manifest was created with a private key
-        if (SecurityKey is RsaSecurityKey rsa && rsa.PrivateKeyStatus == PrivateKeyStatus.Exists)
-        {
-            signing ??= new SigningCredentials(rsa, SecurityAlgorithms.RsaSha256);
-
-            // Ensure we never serialize the private key
-            jwk = JsonWebKeyConverter.ConvertFromRSASecurityKey(new RsaSecurityKey(rsa.Rsa.ExportParameters(false)));
-        }
-
-        var token = new JwtSecurityToken(
-            claims:
-                new[] { new Claim(JwtRegisteredClaimNames.Iss, Issuer) }
-                .Concat(Audience.Select(x => new Claim(JwtRegisteredClaimNames.Aud, x)))
-                .Concat(
-                [
-                    // See https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.6
-                    new(JwtRegisteredClaimNames.Iat, Math.Truncate((DateTime.UtcNow - DateTime.UnixEpoch).TotalSeconds).ToString()),
-                    new("client_id", ClientId),
-                    // non-standard claim containing the base64-encoded public key
-                    new("pub", PublicKey),
-                    // standard claim, serialized as a JSON string, not an encoded JSON object
-                    new("sub_jwk", JsonSerializer.Serialize(jwk, JsonOptions.JsonWebKey), JsonClaimValueTypes.Json),
-                ]),
-            signingCredentials: signing);
-
-        return new JwtSecurityTokenHandler().WriteToken(token);
-    }
-
-    /// <summary>
-    /// Sign the JWT claims with the provided RSA key.
-    /// </summary>
-    public string Sign(IEnumerable<Claim> claims, RSA rsa, TimeSpan? expiration = default)
-        => Sign(claims, new RsaSecurityKey(rsa), expiration);
-
-    public string Sign(IEnumerable<Claim> claims, RsaSecurityKey? key = default, TimeSpan? expiration = default)
-    {
-        var rsa = key ?? SecurityKey as RsaSecurityKey;
-        if (rsa?.PrivateKeyStatus != PrivateKeyStatus.Exists)
-            throw new NotSupportedException("No private key found to sign the manifest.");
-
-        var signing = new SigningCredentials(rsa, SecurityAlgorithms.RsaSha256);
-
-        var expirationDate = expiration != null ?
-            DateTime.UtcNow.Add(expiration.Value) :
-            // Expire the first day of the next month
-            new DateTime(
-                DateTime.UtcNow.AddMonths(1).Year,
-                DateTime.UtcNow.AddMonths(1).Month, 1,
-                // Use current time so they don't expire all at the same time
-                DateTime.UtcNow.Hour,
-                DateTime.UtcNow.Minute,
-                DateTime.UtcNow.Second,
-                DateTime.UtcNow.Millisecond,
-                DateTimeKind.Utc);
-
-        var tokenClaims = claims.Where(x => x.Type != JwtRegisteredClaimNames.Iat && x.Type != JwtRegisteredClaimNames.Exp).ToList();
-
-        // See https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.6
-        tokenClaims.Add(new(JwtRegisteredClaimNames.Iat, Math.Truncate((DateTime.UtcNow - DateTime.UnixEpoch).TotalSeconds).ToString()));
-
-        if (tokenClaims.Find(c => c.Type == JwtRegisteredClaimNames.Iss) is { } issuer)
-        {
-            if (issuer.Value != Issuer)
-                throw new ArgumentException($"The received claims contain an incompatible 'iss' claim. If present, the claim must contain the value '{Issuer}' but was '{issuer.Value}'.");
-        }
-        else
-        {
-            tokenClaims.Insert(0, new(JwtRegisteredClaimNames.Iss, Issuer));
-        }
-
-        if (tokenClaims.Find(c => c.Type == "client_id") is { } clientId)
-        {
-            if (clientId.Value != ClientId)
-                throw new ArgumentException($"The received claims contain an incompatible 'client_id' claim. If present, the claim must contain the value '{ClientId}' but was '{clientId.Value}'.");
-        }
-        else
-        {
-            tokenClaims.Add(new("client_id", ClientId));
-        }
-
-        // Avoid duplicating audience claims
-        foreach (var audience in Audience)
-        {
-            // Always compare ignoring trailing /
-            if (tokenClaims.Find(c => c.Type == JwtRegisteredClaimNames.Aud && c.Value.TrimEnd('/') == audience.TrimEnd('/')) == null)
-                tokenClaims.Insert(1, new(JwtRegisteredClaimNames.Aud, audience));
-        }
-
-        // The other claims (client_id, pub, sub_jwk) claims are mostly for the SL manifest itself,
-        // not for the user, so for now we don't add them. 
-
-        // Don't allow mismatches of public manifest key and the one used to sign, to avoid 
-        // weird run-time errors verifiying manifests that were signed with a different key.
-        var pubKey = Convert.ToBase64String(rsa.Rsa.ExportRSAPublicKey());
-        if (pubKey != PublicKey)
-            throw new ArgumentException($"Cannot sign with a private key that does not match the manifest public key.");
-
-        var jwt = new JwtSecurityTokenHandler().WriteToken(new JwtSecurityToken(
-            claims: tokenClaims,
-            expires: expirationDate,
-            signingCredentials: signing
-        ));
-
-        return jwt;
-    }
-
-    public ClaimsPrincipal Validate(string jwt, out SecurityToken? token) => new JwtSecurityTokenHandler().ValidateToken(jwt, new TokenValidationParameters
-    {
-        RequireExpirationTime = true,
-        // NOTE: setting this to false allows checking sponsorships even when the manifest is expired. 
-        // This might be useful if package authors want to extend the manifest lifetime beyond the default 
-        // 30 days and issue a warning on expiration, rather than an error and a forced sync.
-        // If this is not set (or true), a SecurityTokenExpiredException exception will be thrown.
-        ValidateLifetime = false,
-        RequireAudience = true,
-        // At least one of the audiences must match the manifest audiences
-        AudienceValidator = (audiences, _, _) => Audience.Intersect(audiences.Select(x => x.TrimEnd('/'))).Any(),
-        ValidIssuer = Issuer,
-        IssuerSigningKey = SecurityKey,
-    }, out token);
-
-    /// <summary>
-    /// Gets the GitHub sponsorable account.
-    /// </summary>
-    public string Sponsorable { get; }
-
-    /// <summary>
-    /// The web endpoint that issues signed JWT to authenticated users.
-    /// </summary>
-    /// <remarks>
-    /// See https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.1
-    /// </remarks>
-    public string Issuer { get; }
-
-    /// <summary>
-    /// The audience for the JWT, which includes the sponsorable account and potentially other sponsoring platforms.
-    /// </summary>
-    /// <remarks>
-    /// See https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.3
-    /// </remarks>
-    public string[] Audience { get; }
-
-    /// <summary>
-    /// The OAuth client ID (i.e. GitHub OAuth App ID) that is used to 
-    /// authenticate the user.
-    /// </summary>
-    /// <remarks>
-    /// See https://www.rfc-editor.org/rfc/rfc8693.html#name-client_id-client-identifier
-    /// </remarks>
-    public string ClientId { get; internal set; }
-
-    /// <summary>
-    /// Public key that can be used to verify JWT signatures.
-    /// </summary>
-    public string PublicKey { get; }
-
-    /// <summary>
-    /// Public key in a format that can be used to verify JWT signatures.
-    /// </summary>
-    public SecurityKey SecurityKey { get; }
-
-    /// <inheritdoc/>
-    public override int GetHashCode() => new HashCode().Add(Issuer, ClientId, PublicKey).AddRange(Audience).ToHashCode();
-
-    /// <inheritdoc/>
-    public override bool Equals(object? obj) => obj is SponsorableManifest other && GetHashCode() == other.GetHashCode();
-}
diff --git a/src/SponsorLink/Tests/Tests.csproj b/src/SponsorLink/Tests/Tests.csproj
deleted file mode 100644
index f753aad..0000000
--- a/src/SponsorLink/Tests/Tests.csproj
+++ /dev/null
@@ -1,42 +0,0 @@
-<Project Sdk="Microsoft.NET.Sdk">
-
-  <PropertyGroup>
-    <TargetFramework>net8.0</TargetFramework>
-  </PropertyGroup>
-
-  <ItemGroup>
-    <PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
-    <PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="8.0.0" />
-    <PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
-    <PackageReference Include="Microsoft.IdentityModel.Tokens" Version="7.6.0" />
-    <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.6.0" />
-    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
-    <PackageReference Include="xunit" Version="2.8.1" />
-    <PackageReference Include="xunit.runner.visualstudio" Version="2.8.1" />
-    <PackageReference Include="Humanizer.Core" Version="2.14.1" />
-    <PackageReference Include="Humanizer.Core.es" Version="2.14.1" />
-  </ItemGroup>
-
-  <ItemGroup>
-    <!-- This project reference allows debugging the source generator/analyzer project -->
-    <ProjectReference Include="..\Analyzer\Analyzer.csproj" Aliases="Analyzer" />
-    <Analyzer Include="..\Analyzer\bin\$(Configuration)\netstandard2.0\*.dll" />
-  </ItemGroup>
-
-  <Target Name="Sample_GitConfig" Condition="'$(SourceControlInformationFeatureSupported)' == 'true'" DependsOnTargets="InitializeSourceControlInformation">
-    <!-- Follows recommendation from Microsoft.Common.CurrentVersion.targets on how to depend on SCC info. -->
-    <ItemGroup>
-      <GitRoot Include="@(SourceRoot -&gt; WithMetadataValue('SourceControl', 'git'))" />
-    </ItemGroup>
-    <PropertyGroup>
-      <GitRoot>%(GitRoot.FullPath)</GitRoot>
-    </PropertyGroup>
-    <ItemGroup Condition="'$(GitRoot)' != ''">
-      <AdditionalFiles Include="$([System.IO.Path]::GetFullPath($(GitRoot).git/config))" SourceItemType="GitConfig" />
-      <AdditionalFiles Include="$([System.IO.Path]::GetFullPath($(UserProfileHome)/.gitconfig))" SourceItemType="GitConfig" />
-    </ItemGroup>
-  </Target>
-
-  <Import Project="..\SponsorLink\buildTransitive\Devlooped.Sponsors.targets" />
-
-</Project>
\ No newline at end of file
diff --git a/src/SponsorLink/readme.md b/src/SponsorLink/readme.md
deleted file mode 100644
index cb651a1..0000000
--- a/src/SponsorLink/readme.md
+++ /dev/null
@@ -1,34 +0,0 @@
-# SponsorLink .NET Analyzer
-
-This is one opinionated implementation of [SponsorLink](https://devlooped.com/SponsorLink) 
-for .NET projects leveraging Roslyn analyzers.
-
-It is intended for use by [devlooped](https://github.com/devlooped) projects, but can be 
-used as a template for other sponsorables as well. Supporting arbitrary sponsoring scenarios 
-is out of scope though, since we just use GitHub sponsors for now.
-
-## Usage
-
-A project initializing from this template repo via [dotnet-file](https://github.com/devlooped/dotnet-file) 
-will have all the sources cloned under `src\SponsorLink`. 
-
-Including the analyzer and targets in a project involves two steps. 
-
-1. Create an analyzer project and add the following property:
-
-```xml
-  <PropertyGroup>
-    ...
-    <CustomAfterMicrosoftCSharpTargets>$(MSBuildThisFileDirectory)..\SponsorLink\SponsorLink.targets</CustomAfterMicrosoftCSharpTargets>
-  </PropertyGroup>
-```
-
-2. Add a `buildTransitive\[PackageId].targets` file with the following import:
-
-```xml
-<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <Import Project="Devlooped.Sponsors.targets"/>
-</Project>
-```
-
-As long as NuGetizer is used, the right packaging will be done automatically.
\ No newline at end of file