diff --git a/.docfx/docfx.json b/.docfx/docfx.json
index 9944a58..291ae32 100644
--- a/.docfx/docfx.json
+++ b/.docfx/docfx.json
@@ -47,7 +47,7 @@
],
"globalMetadata": {
"_appTitle": "Extensions for BenchmarkDotNet by Codebelt",
- "_appFooter": "Generated by DocFX. Copyright 2025-2026 Geekle. All rights reserved.",
+ "_appFooter": "Generated by DocFX. Copyright 2025-2026 Geekle. All rights reserved.",
"_appLogoPath": "images/50x50.png",
"_appFaviconPath": "images/favicon.ico",
"_googleAnalyticsTagId": "G-K2NG2TXDWQ",
diff --git a/.github/dispatch-targets.json b/.github/dispatch-targets.json
new file mode 100644
index 0000000..1e3ec72
--- /dev/null
+++ b/.github/dispatch-targets.json
@@ -0,0 +1 @@
+[ ]
diff --git a/.github/workflows/service-update.yml b/.github/workflows/service-update.yml
new file mode 100644
index 0000000..d08f940
--- /dev/null
+++ b/.github/workflows/service-update.yml
@@ -0,0 +1,139 @@
+name: Service Update
+
+on:
+ repository_dispatch:
+ types: [codebelt-service-update]
+ workflow_dispatch:
+ inputs:
+ source_repo:
+ description: 'Triggering source repo name (e.g. cuemon)'
+ required: false
+ default: ''
+ source_version:
+ description: 'Version released by source (e.g. 10.3.0)'
+ required: false
+ default: ''
+ dry_run:
+ type: boolean
+ description: 'Dry run — show changes but do not commit or open PR'
+ default: false
+
+permissions:
+ contents: write
+ pull-requests: write
+
+jobs:
+ service-update:
+ runs-on: ubuntu-24.04
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Resolve trigger inputs
+ id: trigger
+ run: |
+ SOURCE="${{ github.event.client_payload.source_repo || github.event.inputs.source_repo }}"
+ VERSION="${{ github.event.client_payload.source_version || github.event.inputs.source_version }}"
+ echo "source=$SOURCE" >> $GITHUB_OUTPUT
+ echo "version=$VERSION" >> $GITHUB_OUTPUT
+
+ - name: Determine new version for this repo
+ id: newver
+ run: |
+ CURRENT=$(grep -oP '(?<=## \[)[\d.]+(?=\])' CHANGELOG.md | head -1)
+ NEW=$(echo "$CURRENT" | awk -F. '{printf "%s.%s.%d", $1, $2, $3+1}')
+ BRANCH="v${NEW}/service-update"
+ echo "current=$CURRENT" >> $GITHUB_OUTPUT
+ echo "new=$NEW" >> $GITHUB_OUTPUT
+ echo "branch=$BRANCH" >> $GITHUB_OUTPUT
+
+ - name: Generate codebelt-aicia token
+ id: app-token
+ uses: actions/create-github-app-token@v1
+ with:
+ app-id: ${{ vars.CODEBELT_AICIA_APP_ID }}
+ private-key: ${{ secrets.CODEBELT_AICIA_PRIVATE_KEY }}
+ owner: codebeltnet
+
+ - name: Bump NuGet packages
+ run: python3 .github/scripts/bump-nuget.py
+ env:
+ TRIGGER_SOURCE: ${{ steps.trigger.outputs.source }}
+ TRIGGER_VERSION: ${{ steps.trigger.outputs.version }}
+
+ - name: Update PackageReleaseNotes.txt
+ run: |
+ NEW="${{ steps.newver.outputs.new }}"
+ for f in .nuget/*/PackageReleaseNotes.txt; do
+ [ -f "$f" ] || continue
+ TFM=$(grep -m1 "^Availability:" "$f" | sed 's/Availability: //' || echo ".NET 10, .NET 9 and .NET Standard 2.0")
+ ENTRY="Version ${NEW}\nAvailability: ${TFM}\n \n# ALM\n- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs)\n \n"
+ { printf "$ENTRY"; cat "$f"; } > "$f.tmp" && mv "$f.tmp" "$f"
+ done
+
+ - name: Update CHANGELOG.md
+ run: |
+ python3 - <<'EOF'
+ import os, re
+ from datetime import date
+ new_ver = os.environ['NEW_VERSION']
+ today = date.today().isoformat()
+ entry = f"## [{new_ver}] - {today}\n\nThis is a service update that focuses on package dependencies.\n\n"
+ with open("CHANGELOG.md") as f:
+ content = f.read()
+ idx = content.find("## [")
+ content = (content[:idx] + entry + content[idx:]) if idx != -1 else (content + entry)
+ with open("CHANGELOG.md", "w") as f:
+ f.write(content)
+ print(f"CHANGELOG updated for v{new_ver}")
+ EOF
+ env:
+ NEW_VERSION: ${{ steps.newver.outputs.new }}
+
+ # Note: Docker image bumps removed in favor of manual updates
+ # The automated selection was picking wrong variants (e.g., mono-* instead of standard)
+ # TODO: Move to hosted service for smarter image selection
+
+ - name: Show diff (dry run)
+ if: ${{ github.event.inputs.dry_run == 'true' }}
+ run: git diff
+
+ - name: Create branch and open PR
+ if: ${{ github.event.inputs.dry_run != 'true' }}
+ env:
+ GH_TOKEN: ${{ steps.app-token.outputs.token }}
+ run: |
+ NEW="${{ steps.newver.outputs.new }}"
+ BRANCH="${{ steps.newver.outputs.branch }}"
+ SOURCE="${{ steps.trigger.outputs.source }}"
+ SRC_VER="${{ steps.trigger.outputs.version }}"
+
+ git config user.name "codebelt-aicia[bot]"
+ git config user.email "codebelt-aicia[bot]@users.noreply.github.com"
+ git checkout -b "$BRANCH"
+ git add -A
+ git diff --cached --quiet && echo "Nothing changed - skipping PR." && exit 0
+ git commit -m "V${NEW}/service update"
+ git push origin "$BRANCH"
+
+ echo "This is a service update that focuses on package dependencies." > pr_body.txt
+ echo "" >> pr_body.txt
+ echo "Automated changes:" >> pr_body.txt
+ echo "- Codebelt/Cuemon package versions bumped to latest compatible" >> pr_body.txt
+ echo "- PackageReleaseNotes.txt updated for v${NEW}" >> pr_body.txt
+ echo "- CHANGELOG.md entry added for v${NEW}" >> pr_body.txt
+ echo "" >> pr_body.txt
+ echo "Note: Third-party packages (Microsoft.Extensions.*, BenchmarkDotNet, etc.) are not auto-updated." >> pr_body.txt
+ echo "Use Dependabot or manual updates for those." >> pr_body.txt
+ echo "" >> pr_body.txt
+ echo "Generated by codebelt-aicia" >> pr_body.txt
+ if [ -n "$SOURCE" ] && [ -n "$SRC_VER" ]; then
+ echo "Triggered by: ${SOURCE} @ ${SRC_VER}" >> pr_body.txt
+ else
+ echo "Triggered by: manual workflow dispatch" >> pr_body.txt
+ fi
+
+ gh pr create --title "V${NEW}/service update" --body-file pr_body.txt --base main --head "$BRANCH" --assignee gimlichael
diff --git a/.github/workflows/trigger-downstream.yml b/.github/workflows/trigger-downstream.yml
new file mode 100644
index 0000000..29eb29c
--- /dev/null
+++ b/.github/workflows/trigger-downstream.yml
@@ -0,0 +1,78 @@
+name: Trigger Downstream Service Updates
+
+on:
+ release:
+ types: [published]
+
+jobs:
+ dispatch:
+ if: github.event.release.prerelease == false
+ runs-on: ubuntu-24.04
+ permissions:
+ contents: read
+
+ steps:
+ - name: Checkout (to read dispatch-targets.json)
+ uses: actions/checkout@v4
+
+ - name: Check for dispatch targets
+ id: check
+ run: |
+ if [ ! -f .github/dispatch-targets.json ]; then
+ echo "No dispatch-targets.json found, skipping."
+ echo "has_targets=false" >> $GITHUB_OUTPUT
+ exit 0
+ fi
+ COUNT=$(python3 -c "import json; print(len(json.load(open('.github/dispatch-targets.json'))))")
+ echo "has_targets=$([ $COUNT -gt 0 ] && echo true || echo false)" >> $GITHUB_OUTPUT
+
+ - name: Extract version from release tag
+ if: steps.check.outputs.has_targets == 'true'
+ id: version
+ run: |
+ VERSION="${{ github.event.release.tag_name }}"
+ VERSION="${VERSION#v}"
+ echo "version=$VERSION" >> $GITHUB_OUTPUT
+
+ - name: Generate codebelt-aicia token
+ if: steps.check.outputs.has_targets == 'true'
+ id: app-token
+ uses: actions/create-github-app-token@v1
+ with:
+ app-id: ${{ vars.CODEBELT_AICIA_APP_ID }}
+ private-key: ${{ secrets.CODEBELT_AICIA_PRIVATE_KEY }}
+ owner: codebeltnet
+
+ - name: Dispatch to downstream repos
+ if: steps.check.outputs.has_targets == 'true'
+ run: |
+ python3 - <<'EOF'
+ import json, urllib.request, os, sys
+
+ targets = json.load(open('.github/dispatch-targets.json'))
+ token = os.environ['GH_TOKEN']
+ version = os.environ['VERSION']
+ source = os.environ['SOURCE_REPO']
+
+ for repo in targets:
+ url = f'https://api.github.com/repos/codebeltnet/{repo}/dispatches'
+ payload = json.dumps({
+ 'event_type': 'codebelt-service-update',
+ 'client_payload': {
+ 'source_repo': source,
+ 'source_version': version
+ }
+ }).encode()
+ req = urllib.request.Request(url, data=payload, method='POST', headers={
+ 'Authorization': f'Bearer {token}',
+ 'Accept': 'application/vnd.github+json',
+ 'Content-Type': 'application/json',
+ 'X-GitHub-Api-Version': '2022-11-28'
+ })
+ with urllib.request.urlopen(req) as r:
+ print(f'✓ Dispatched to {repo}: HTTP {r.status}')
+ EOF
+ env:
+ GH_TOKEN: ${{ steps.app-token.outputs.token }}
+ VERSION: ${{ steps.version.outputs.version }}
+ SOURCE_REPO: ${{ github.event.repository.name }}