Files
Stefan Broenner d43a165f85 Preserve Agent Plugins 1.0 source fixes (#811)
* fix(plugin): preserve Agent Plugins 1.0 source

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* docs(plugin): define publication source of truth

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Stefan Broenner <stbrnner@microsoft.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-24 15:40:28 +02:00

642 lines
26 KiB
YAML
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: Publish Plugins
# Phase 3: Publish Copilot CLI plugin artifacts to sbroenne/mcp-server-excel-plugins
#
# TRIGGER: After successful release workflow completion
# - Avoids binary race by waiting for GitHub Release to exist
# - Uses workflow_run to get the version that was just released
#
# WHAT IT DOES:
# 1. Builds both Agent Plugins 1.0 bundles (excel-mcp, excel-cli) from canonical source templates
# 2. Validates that published plugins contain wrapper/bootstrap assets, not bundled runtimes
# 3. Updates version.txt to match the release version
# 4. Pushes published plugin artifacts to sbroenne/mcp-server-excel-plugins
# 5. Rewrites the published repo to the canonical marketplace layout (.github/plugin/marketplace.json, no legacy root manifest)
#
# NOTE:
# - sbroenne/mcp-server-excel-plugins is the actual Copilot CLI marketplace repo
# - this source repo is NOT a marketplace; .github/plugins/ contains the canonical plugin templates copied into the published repo
#
# AUTHENTICATION:
# - Requires repository secret PLUGINS_REPO_TOKEN (PAT or app token)
# - Token must have contents:write on sbroenne/mcp-server-excel-plugins
#
# CONCURRENCY:
# - Only one publish per plugin at a time (prevents race conditions)
# - Does NOT cancel in-progress (let it finish before next publish)
on:
workflow_run:
workflows: ["Release All Components"]
types:
- completed
branches:
- main
workflow_dispatch:
inputs:
release_tag:
description: Existing source release tag to re-sync (for example v1.2.3)
required: true
type: string
permissions:
contents: read
concurrency:
group: publish-plugins-${{ github.ref }}
cancel-in-progress: false
env:
PUBLISHED_REPO_OWNER: sbroenne
PUBLISHED_REPO_NAME: mcp-server-excel-plugins
PUBLISHED_REPO: sbroenne/mcp-server-excel-plugins
PUBLISHED_REPO_URL: https://github.com/sbroenne/mcp-server-excel-plugins
jobs:
# =============================================================================
# Job 0: Validate cross-repo publish configuration
# =============================================================================
preflight:
name: Validate Publish Configuration
runs-on: ubuntu-latest
if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
steps:
- name: Verify required token configuration exists
env:
PLUGINS_REPO_TOKEN: ${{ secrets.PLUGINS_REPO_TOKEN }}
run: |
if [ -z "$PLUGINS_REPO_TOKEN" ]; then
echo "::error::Missing required Actions secret PLUGINS_REPO_TOKEN."
echo "::error::Store a PAT or app token with contents:write on ${{ env.PUBLISHED_REPO }} in repository secrets for this repo."
exit 1
fi
- name: Verify published repo is reachable
env:
GH_TOKEN: ${{ secrets.PLUGINS_REPO_TOKEN }}
run: gh api repos/${{ env.PUBLISHED_REPO }} --silent
# =============================================================================
# Job 1: Get Version from Triggering Release Workflow
# =============================================================================
get-version:
name: Get Release Version
runs-on: ubuntu-latest
needs: [preflight]
if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
outputs:
version: ${{ steps.extract_version.outputs.version }}
tag: ${{ steps.extract_version.outputs.tag }}
trigger_mode: ${{ steps.extract_version.outputs.trigger_mode }}
steps:
- name: Checkout source repo with tags
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Resolve release tag and version
id: extract_version
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
TAG="${{ inputs.release_tag }}"
if ! printf '%s' "$TAG" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.-]+)?$'; then
echo "::error::Manual re-sync requires a full source release tag like v1.2.3."
exit 1
fi
gh release view "$TAG" --repo "${{ github.repository }}" >/dev/null
VERSION="${TAG#v}"
TRIGGER_MODE="manual"
echo "Manual plugin re-sync requested for tag: $TAG"
else
HEAD_SHA="${{ github.event.workflow_run.head_sha }}"
echo "Triggering workflow HEAD commit: $HEAD_SHA"
TAG=""
for attempt in 1 2 3; do
git fetch --force --tags origin >/dev/null 2>&1
TAG=$(git tag --points-at "$HEAD_SHA" --sort=-version:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.-]+)?$' | head -n1 || true)
if [ -n "$TAG" ]; then
break
fi
if [ "$attempt" -lt 3 ]; then
echo "No source release tag visible for $HEAD_SHA yet; retrying in 5 seconds..."
sleep 5
fi
done
if [ -z "$TAG" ]; then
echo "::error::No source release tag points at commit $HEAD_SHA."
echo "::error::Expected an annotated source release tag (for example v1.2.3) created by release.yml."
exit 1
fi
VERSION="${TAG#v}"
TRIGGER_MODE="automatic"
fi
echo "Resolved release tag: $TAG"
echo "Resolved version: $VERSION"
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "tag=$TAG" >> $GITHUB_OUTPUT
echo "trigger_mode=$TRIGGER_MODE" >> $GITHUB_OUTPUT
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# =============================================================================
# Job 2: Source-side sync gate
# =============================================================================
detect-plugin-surface:
name: Detect Plugin Surface Changes
runs-on: ubuntu-latest
needs: [get-version]
outputs:
should_publish: ${{ steps.diff.outputs.should_publish }}
reason: ${{ steps.diff.outputs.reason }}
steps:
- name: Checkout source repo with tags
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Evaluate publish-worthy source changes
id: diff
run: |
CURRENT_TAG="${{ needs.get-version.outputs.tag }}"
TRIGGER_MODE="${{ needs.get-version.outputs.trigger_mode }}"
if [ "$TRIGGER_MODE" = "manual" ]; then
REASON="Manual re-sync requested for $CURRENT_TAG."
echo "should_publish=true" >> "$GITHUB_OUTPUT"
echo "reason=$REASON" >> "$GITHUB_OUTPUT"
{
echo "## Plugin Surface Gate"
echo ""
echo "- Trigger: manual re-sync"
echo "- Decision: $REASON"
} >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
PREVIOUS_TAG=$(git tag --list 'v*' --sort=-version:refname | grep -Fxv "$CURRENT_TAG" | head -n1 || true)
if [ -z "$PREVIOUS_TAG" ]; then
REASON="No previous release tag found before $CURRENT_TAG; publishing initial plugin surface."
echo "should_publish=true" >> "$GITHUB_OUTPUT"
echo "reason=$REASON" >> "$GITHUB_OUTPUT"
{
echo "## Plugin Surface Gate"
echo ""
echo "- Current tag: $CURRENT_TAG"
echo "- Previous tag: none"
echo "- Decision: $REASON"
} >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
mapfile -t CHANGED_FILES < <(git diff --name-only "$PREVIOUS_TAG" "$CURRENT_TAG" -- .github/plugins .github/workflows/publish-plugins.yml scripts/Build-Plugins.ps1 scripts/Sync-PublishedPluginRepo.ps1 skills/excel-cli skills/excel-mcp skills/shared)
if [ "${#CHANGED_FILES[@]}" -eq 0 ]; then
REASON="No plugin-published source files changed between $PREVIOUS_TAG and $CURRENT_TAG; skipping downstream publish."
echo "should_publish=false" >> "$GITHUB_OUTPUT"
echo "reason=$REASON" >> "$GITHUB_OUTPUT"
{
echo "## Plugin Surface Gate"
echo ""
echo "- Current tag: $CURRENT_TAG"
echo "- Previous tag: $PREVIOUS_TAG"
echo "- Decision: $REASON"
} >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
REASON="Detected ${#CHANGED_FILES[@]} plugin-published source file(s) between $PREVIOUS_TAG and $CURRENT_TAG."
echo "should_publish=true" >> "$GITHUB_OUTPUT"
echo "reason=$REASON" >> "$GITHUB_OUTPUT"
{
echo "## Plugin Surface Gate"
echo ""
echo "- Current tag: $CURRENT_TAG"
echo "- Previous tag: $PREVIOUS_TAG"
echo "- Decision: $REASON"
echo ""
echo "### Changed publish surface"
for file in "${CHANGED_FILES[@]}"; do
echo "- $file"
done
} >> "$GITHUB_STEP_SUMMARY"
# =============================================================================
# Job 3: Build Plugin Artifacts
# =============================================================================
build-plugins:
name: Build Plugin Artifacts
runs-on: windows-latest
needs: [get-version, detect-plugin-surface]
if: ${{ needs.detect-plugin-surface.outputs.should_publish == 'true' }}
steps:
- name: Checkout source repo
uses: actions/checkout@v7
with:
path: source
- name: Setup .NET
uses: actions/setup-dotnet@v6
with:
dotnet-version: '10.0.x'
- name: Setup Python for Agent Skills validation
uses: actions/setup-python@v6
with:
python-version: '3.13'
- name: Test Agent Plugin packaging
run: >
dotnet test source/tests/ExcelMcp.SkillGeneration.Tests/ExcelMcp.SkillGeneration.Tests.csproj
--configuration Release
--filter "Feature=AgentPluginSpec|Feature=PluginBootstrap"
--verbosity minimal
- name: Build plugins from canonical source templates
run: |
$VERSION = "${{ needs.get-version.outputs.version }}"
Write-Host "Building plugins for version: $VERSION"
cd source
& .\scripts\Build-Plugins.ps1 -Version $VERSION
shell: pwsh
- name: Validate Agent Skills with skills-ref
shell: pwsh
run: |
python -m pip install --disable-pip-version-check `
"git+https://github.com/agentskills/agentskills.git@69ef37e9424c0a7ea9dd2293b559e43ec8176379#subdirectory=skills-ref"
skills-ref validate source\plugins\excel-mcp\skills\excel-mcp
skills-ref validate source\plugins\excel-cli\skills\excel-cli
- name: Validate wrapper-only plugin payloads
shell: pwsh
run: |
$blockedNames = @(".exe", ".dll", ".pdb")
$pluginRoots = @(
@{ Name = "excel-mcp"; Path = "source\plugins\excel-mcp" },
@{ Name = "excel-cli"; Path = "source\plugins\excel-cli" }
)
foreach ($plugin in $pluginRoots) {
$pluginPath = $plugin.Path
$pluginName = $plugin.Name
if (-not (Test-Path $pluginPath)) {
throw "Built plugin directory not found: $pluginPath"
}
$runtimePayload = Get-ChildItem -Path $pluginPath -Recurse -Force -File | Where-Object {
$_.Extension -in $blockedNames -or
$_.Name.EndsWith(".deps.json", [System.StringComparison]::OrdinalIgnoreCase) -or
$_.Name.EndsWith(".runtimeconfig.json", [System.StringComparison]::OrdinalIgnoreCase)
}
if ($runtimePayload.Count -gt 0) {
$payloadList = ($runtimePayload | ForEach-Object { $_.FullName }) -join [Environment]::NewLine
throw "Published plugin '$pluginName' still contains committed runtime payload(s):`n$payloadList"
}
Write-Host "Validated wrapper/bootstrap-only payload for $pluginName" -ForegroundColor Green
}
- name: Upload excel-mcp plugin artifact
uses: actions/upload-artifact@v7
with:
name: excel-mcp-plugin
path: source/plugins/excel-mcp/
retention-days: 1
- name: Upload excel-cli plugin artifact
uses: actions/upload-artifact@v7
with:
name: excel-cli-plugin
path: source/plugins/excel-cli/
retention-days: 1
# =============================================================================
# Job 4: Publish to Plugin Repo
# =============================================================================
publish:
name: Publish to Plugin Repo
runs-on: ubuntu-latest
needs: [get-version, detect-plugin-surface, build-plugins]
if: ${{ needs.detect-plugin-surface.outputs.should_publish == 'true' }}
steps:
- name: Download excel-mcp plugin
uses: actions/download-artifact@v8
with:
name: excel-mcp-plugin
path: built-plugins/excel-mcp
- name: Download excel-cli plugin
uses: actions/download-artifact@v8
with:
name: excel-cli-plugin
path: built-plugins/excel-cli
- name: Checkout source repo
uses: actions/checkout@v7
with:
path: source-repo
- name: Checkout published repo
uses: actions/checkout@v7
with:
repository: ${{ env.PUBLISHED_REPO }}
token: ${{ secrets.PLUGINS_REPO_TOKEN }}
path: published-repo
fetch-depth: 0
- name: Get git user info for commits
id: git-user
run: |
echo "user_name=ExcelMcp Publisher Bot" >> "$GITHUB_OUTPUT"
echo "user_email=excelmcp-publisher[bot]@users.noreply.github.com" >> "$GITHUB_OUTPUT"
- name: Validate sync target and published repo state
id: guard
run: |
VERSION="${{ needs.get-version.outputs.version }}"
TAG="${{ needs.get-version.outputs.tag }}"
TRIGGER_MODE="${{ needs.get-version.outputs.trigger_mode }}"
EXPECTED_TAG="v$VERSION"
SEMVER_REGEX='^[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.-]+)?$'
if ! [[ "$VERSION" =~ $SEMVER_REGEX ]]; then
echo "::error::Resolved version '$VERSION' is not a supported semantic version."
exit 1
fi
if [ "$TAG" != "$EXPECTED_TAG" ]; then
echo "::error::Resolved tag '$TAG' does not match version '$VERSION'. Expected '$EXPECTED_TAG'."
exit 1
fi
MANIFEST_MODE=""
MARKETPLACE_MANIFEST=""
if [ -f published-repo/.github/plugin/marketplace.json ]; then
MANIFEST_MODE="canonical"
MARKETPLACE_MANIFEST="published-repo/.github/plugin/marketplace.json"
elif [ -f published-repo/marketplace.json ]; then
MANIFEST_MODE="legacy"
MARKETPLACE_MANIFEST="published-repo/marketplace.json"
else
echo "::error::No marketplace manifest found in published repo. Expected .github/plugin/marketplace.json (canonical) or marketplace.json (legacy)."
exit 1
fi
if [ "$MANIFEST_MODE" = "canonical" ]; then
CURRENT_PUBLISHED_VERSION=$(jq -r '[.plugins[].version] | unique | if length == 1 then .[0] else empty end' "$MARKETPLACE_MANIFEST")
else
CURRENT_PUBLISHED_VERSION=$(jq -r '.version // empty' "$MARKETPLACE_MANIFEST")
fi
if [ -z "$CURRENT_PUBLISHED_VERSION" ]; then
echo "::error::$MARKETPLACE_MANIFEST is missing a resolvable published plugin version."
exit 1
fi
if ! [[ "$CURRENT_PUBLISHED_VERSION" =~ $SEMVER_REGEX ]]; then
echo "::error::Published repo version '$CURRENT_PUBLISHED_VERSION' is not a supported semantic version."
exit 1
fi
for plugin in excel-mcp excel-cli; do
ARTIFACT_VERSION=$(jq -r '.version // empty' "built-plugins/$plugin/plugin.json")
if [ "$ARTIFACT_VERSION" != "$VERSION" ]; then
echo "::error::built-plugins/$plugin/plugin.json resolved version '$ARTIFACT_VERSION' but expected '$VERSION'."
exit 1
fi
if ! jq -e '
."$schema" == "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json"
and (.repository | type == "string")
and ((keys - ["$schema", "name", "version", "description", "author", "homepage", "repository", "license", "keywords", "extensions"]) | length == 0)
' "built-plugins/$plugin/plugin.json" >/dev/null; then
echo "::error::built-plugins/$plugin/plugin.json is not Agent Plugins 1.0 conformant."
exit 1
fi
if [ -f "built-plugins/$plugin/.mcp.json" ]; then
echo "::error::built-plugins/$plugin contains legacy .mcp.json."
exit 1
fi
done
if ! jq -e '
."$schema" == "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json"
and ((keys - ["$schema", "mcpServers"]) | length == 0)
and (.mcpServers["excel-mcp"].type == "stdio")
and (.mcpServers["excel-mcp"].command == "powershell")
and (.mcpServers["excel-mcp"].args | index("${PLUGIN_ROOT}/bin/start-mcp.ps1") != null)
' built-plugins/excel-mcp/mcp.json >/dev/null; then
echo "::error::built-plugins/excel-mcp/mcp.json is not Agent Plugins 1.0 conformant."
exit 1
fi
MCP_VERSION_TXT=$(tr -d '\r\n' < built-plugins/excel-mcp/version.txt)
if [ "$MCP_VERSION_TXT" != "$VERSION" ]; then
echo "::error::built-plugins/excel-mcp/version.txt resolved version '$MCP_VERSION_TXT' but expected '$VERSION'."
exit 1
fi
CLI_VERSION_TXT=$(tr -d '\r\n' < built-plugins/excel-cli/version.txt)
if [ "$CLI_VERSION_TXT" != "$VERSION" ]; then
echo "::error::built-plugins/excel-cli/version.txt resolved version '$CLI_VERSION_TXT' but expected '$VERSION'."
exit 1
fi
TAG_EXISTS=false
if git -C published-repo rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then
TAG_EXISTS=true
fi
if [ "$CURRENT_PUBLISHED_VERSION" != "$VERSION" ] && [ "$(printf '%s\n%s\n' "$CURRENT_PUBLISHED_VERSION" "$VERSION" | sort -V | tail -n1)" = "$CURRENT_PUBLISHED_VERSION" ]; then
echo "::error::Published repo version $CURRENT_PUBLISHED_VERSION is newer than requested version $VERSION. Downgrade publish blocked."
exit 1
fi
if [ "$TAG_EXISTS" = true ] && [ "$CURRENT_PUBLISHED_VERSION" != "$VERSION" ]; then
echo "::error::Published repo already contains tag $TAG but ${MARKETPLACE_MANIFEST#published-repo/} reports version $CURRENT_PUBLISHED_VERSION. Refusing to publish into inconsistent state."
exit 1
fi
SYNC_MODE="publish"
REASON="Ready to publish $TAG."
if [ "$CURRENT_PUBLISHED_VERSION" = "$VERSION" ] && [ "$TAG_EXISTS" = true ] && [ "$TRIGGER_MODE" != "manual" ]; then
SYNC_MODE="skip"
REASON="Published repo already has $TAG; automatic duplicate publish skipped."
elif [ "$CURRENT_PUBLISHED_VERSION" = "$VERSION" ] && [ "$TAG_EXISTS" = true ]; then
SYNC_MODE="resync"
REASON="Manual re-sync allowed for existing published version $TAG."
elif [ "$CURRENT_PUBLISHED_VERSION" = "$VERSION" ] && [ "$TAG_EXISTS" = false ]; then
SYNC_MODE="tag-repair"
REASON="Published content is already at version $VERSION, but tag $TAG is missing."
fi
echo "current_published_version=$CURRENT_PUBLISHED_VERSION" >> "$GITHUB_OUTPUT"
echo "marketplace_manifest=$MARKETPLACE_MANIFEST" >> "$GITHUB_OUTPUT"
echo "manifest_mode=$MANIFEST_MODE" >> "$GITHUB_OUTPUT"
echo "tag_exists=$TAG_EXISTS" >> "$GITHUB_OUTPUT"
echo "sync_mode=$SYNC_MODE" >> "$GITHUB_OUTPUT"
echo "reason=$REASON" >> "$GITHUB_OUTPUT"
{
echo "## Publish Guard"
echo ""
echo "- Requested tag: $TAG"
echo "- Requested version: $VERSION"
echo "- Trigger mode: $TRIGGER_MODE"
echo "- Manifest mode: $MANIFEST_MODE"
echo "- Marketplace manifest: ${MARKETPLACE_MANIFEST#published-repo/}"
echo "- Published repo version: $CURRENT_PUBLISHED_VERSION"
echo "- Published tag exists: $TAG_EXISTS"
echo "- Decision: $REASON"
} >> "$GITHUB_STEP_SUMMARY"
- name: Sync plugins to published repo
id: sync
if: steps.guard.outputs.sync_mode != 'skip'
shell: pwsh
run: |
$version = "${{ needs.get-version.outputs.version }}"
$tag = "${{ needs.get-version.outputs.tag }}"
Write-Host "Syncing plugins for version $version (tag: $tag)"
& ./source-repo/scripts/Sync-PublishedPluginRepo.ps1 `
-PublishedRepoDir ./published-repo `
-BuiltPluginsDir ./built-plugins `
-Version $version
git -C published-repo add -A
$previousNativeCommandPreference = $PSNativeCommandUseErrorActionPreference
$stagedDiffExitCode = 0
try {
$PSNativeCommandUseErrorActionPreference = $false
git -C published-repo diff --staged --quiet
$stagedDiffExitCode = $LASTEXITCODE
}
finally {
$PSNativeCommandUseErrorActionPreference = $previousNativeCommandPreference
}
if ($stagedDiffExitCode -eq 1) {
$global:LASTEXITCODE = 0
}
if ($stagedDiffExitCode -eq 0) {
Write-Host "No changes to commit (plugins already up to date)"
"has_changes=false" >> $env:GITHUB_OUTPUT
}
elseif ($stagedDiffExitCode -eq 1) {
"has_changes=true" >> $env:GITHUB_OUTPUT
}
else {
throw "git diff --staged --quiet failed with exit code $stagedDiffExitCode"
}
- name: Commit and push changes
if: steps.guard.outputs.sync_mode != 'skip' && steps.sync.outputs.has_changes == 'true'
run: |
VERSION="${{ needs.get-version.outputs.version }}"
TAG="${{ needs.get-version.outputs.tag }}"
cd published-repo
# Configure git
git config user.name "${{ steps.git-user.outputs.user_name }}"
git config user.email "${{ steps.git-user.outputs.user_email }}"
# Commit changes
git commit \
-m "Release $TAG" \
-m "Published plugins synchronized from sbroenne/mcp-server-excel release $TAG" \
-m "excel-mcp plugin updated to v$VERSION; excel-cli plugin updated to v$VERSION; canonical marketplace manifest rewritten at .github/plugin/marketplace.json" \
-m "Source: ${{ github.server_url }}/${{ github.repository }}/releases/tag/$TAG"
# Push to main
git push origin main
echo "✅ Published plugins to ${{ env.PUBLISHED_REPO_URL }}"
- name: Create plugin repo tag
if: steps.guard.outputs.sync_mode != 'skip' && steps.guard.outputs.tag_exists != 'true'
run: |
TAG="${{ needs.get-version.outputs.tag }}"
cd published-repo
# Create and push tag to published repo
git tag -a "$TAG" -m "Release $TAG"
git push origin "$TAG"
echo "✅ Created tag $TAG in published repo"
- name: Summary
run: |
VERSION="${{ needs.get-version.outputs.version }}"
TAG="${{ needs.get-version.outputs.tag }}"
SYNC_MODE="${{ steps.guard.outputs.sync_mode }}"
REASON="${{ steps.guard.outputs.reason }}"
if [ "$SYNC_MODE" = "skip" ]; then
echo "## ℹ️ Duplicate Publish Skipped" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "$REASON" >> $GITHUB_STEP_SUMMARY
elif [ "${{ steps.sync.outputs.has_changes }}" = "true" ]; then
echo "## ✅ Plugins Published Successfully" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Version:** $VERSION" >> $GITHUB_STEP_SUMMARY
echo "**Tag:** $TAG" >> $GITHUB_STEP_SUMMARY
echo "**Repository:** [${{ env.PUBLISHED_REPO }}](${{ env.PUBLISHED_REPO_URL }})" >> $GITHUB_STEP_SUMMARY
echo "**Mode:** $SYNC_MODE" >> $GITHUB_STEP_SUMMARY
echo "**Marketplace manifest:** \`.github/plugin/marketplace.json\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Published Plugins" >> $GITHUB_STEP_SUMMARY
echo "- ✅ excel-mcp (wrapper/bootstrap assets only)" >> $GITHUB_STEP_SUMMARY
echo "- ✅ excel-cli (wrapper/bootstrap assets only)" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Copilot CLI Installation Examples" >> $GITHUB_STEP_SUMMARY
echo '```powershell' >> $GITHUB_STEP_SUMMARY
echo "# Register marketplace (one-time)" >> $GITHUB_STEP_SUMMARY
echo "copilot plugin marketplace add ${{ env.PUBLISHED_REPO }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "# Install MCP plugin" >> $GITHUB_STEP_SUMMARY
echo "copilot plugin install excel-mcp@mcp-server-excel-plugins" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "# OR install CLI plugin" >> $GITHUB_STEP_SUMMARY
echo "copilot plugin install excel-cli@mcp-server-excel-plugins" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Both published plugins bootstrap the newest self-contained Windows runtime from the main GitHub Releases feed on first use." >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "These published plugin artifacts may also be relevant to other plugin-capable clients, but their installation UX and marketplace wiring are client-specific and are not configured by this workflow." >> $GITHUB_STEP_SUMMARY
else
echo "## ℹ️ Plugin Publish Already In Sync" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "$REASON" >> $GITHUB_STEP_SUMMARY
if [ "${{ steps.guard.outputs.tag_exists }}" != "true" ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "Missing published tag was created without changing plugin files." >> $GITHUB_STEP_SUMMARY
fi
fi