diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 4f8d4ba2a..ef99958b1 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,7 +9,8 @@ { "name": "microsoft-finops-toolkit", "version": "15.0.0", - "source": "./plugins/microsoft-finops-toolkit", + "source": "./src/templates/agent-plugin", + "strict": true, "description": "Microsoft FinOps toolkit plugin for AI-powered cloud financial management.", "category": "finops", "homepage": "https://aka.ms/finops/toolkit" diff --git a/.github/plugin/marketplace.json b/.github/plugin/marketplace.json index 49876e25b..733c5b42f 100644 --- a/.github/plugin/marketplace.json +++ b/.github/plugin/marketplace.json @@ -11,7 +11,7 @@ { "name": "microsoft-finops-toolkit", "version": "15.0.0", - "source": "./plugins/microsoft-finops-toolkit", + "source": "./src/templates/agent-plugin", "description": "Microsoft FinOps toolkit plugin for AI-powered cloud financial management.", "category": "finops", "homepage": "https://aka.ms/finops/toolkit" diff --git a/src/powershell/Tests/Unit/AgentPlugins.Tests.ps1 b/src/powershell/Tests/Unit/AgentPlugins.Tests.ps1 index ccb2a66d4..0edc0e910 100644 --- a/src/powershell/Tests/Unit/AgentPlugins.Tests.ps1 +++ b/src/powershell/Tests/Unit/AgentPlugins.Tests.ps1 @@ -43,16 +43,26 @@ Describe 'Agent plugin manifest' { Join-Path $script:Plugin ($root.agents -replace '^\./', '') | Should -Exist } - It 'Loads MCP servers from .mcp.json in the plugin root' { + It 'Lets Claude auto-discover .mcp.json from the plugin root' { $root = Get-Content (Join-Path $script:Plugin 'plugin.json') -Raw | ConvertFrom-Json $claude = Get-Content (Join-Path $script:Plugin '.claude-plugin/plugin.json') -Raw | ConvertFrom-Json $root.mcpServers | Should -Be '.mcp.json' $claude.mcpServers | Should -BeNullOrEmpty - $claude.agents | Should -BeNullOrEmpty Join-Path $script:Plugin '.mcp.json' | Should -Exist } + It 'Declares resolvable paths for every Claude component' { + $claude = Get-Content (Join-Path $script:Plugin '.claude-plugin/plugin.json') -Raw | ConvertFrom-Json + $paths = @($claude.commands) + @($claude.skills) + @($claude.outputStyles) + + $paths.Count | Should -BeGreaterOrEqual 3 + $paths | ForEach-Object { + $_ | Should -Not -BeNullOrEmpty + Join-Path $script:Plugin $_ | Should -Exist + } + } + It 'Uses unpinned Azure MCP latest package in .mcp.json' { $mcp = Get-Content (Join-Path $script:Plugin '.mcp.json') -Raw | ConvertFrom-Json $args = $mcp.mcpServers.'azure-mcp-server'.args @@ -64,15 +74,23 @@ Describe 'Agent plugin manifest' { } Describe 'Agent plugin components' { - It 'Ships agent definitions as NAME.agent.md files' { - $agents = Get-ChildItem (Join-Path $script:Plugin 'agents') -Filter '*.agent.md' + It 'Ships agent definitions as Claude-compatible NAME.md files' { + $agents = Get-ChildItem (Join-Path $script:Plugin 'agents') -Filter '*.md' + $agents.Count | Should -BeGreaterThan 0 + $agents.Name | ForEach-Object { + $_ | Should -Not -Match '\.agent\.md$' + } } It 'Gives every agent a name and description in front matter' { - Get-ChildItem (Join-Path $script:Plugin 'agents') -Filter '*.agent.md' | ForEach-Object { + Get-ChildItem (Join-Path $script:Plugin 'agents') -Filter '*.md' | ForEach-Object { $content = Get-Content $_.FullName -Raw + $name = [regex]::Match($content, '(?m)^name:\s*(?\S+)\s*$') + $content | Should -Match '(?ms)^---\s.*^name:\s*\S.*^description:\s*\S.*^---' + $name.Success | Should -BeTrue + $_.BaseName | Should -Be $name.Groups['name'].Value } } @@ -164,17 +182,21 @@ Describe 'Plugin discovery and marketplaces' { Join-Path $pluginRoot 'plugin.json' | Should -Exist } - It 'Uses repository-root-relative marketplace sources' { - $marketplaces = @{ - '.github/plugin/marketplace.json' = './plugins/microsoft-finops-toolkit' - '.claude-plugin/marketplace.json' = './plugins/microsoft-finops-toolkit' - } + It 'Uses a cross-platform source path in both marketplaces' { + $marketplaces = @('.github/plugin/marketplace.json', '.claude-plugin/marketplace.json') - foreach ($marketplace in $marketplaces.Keys) + foreach ($marketplace in $marketplaces) { $json = Get-Content (Join-Path $script:RepoRoot $marketplace) -Raw | ConvertFrom-Json $entry = $json.plugins | Where-Object { $_.name -eq 'microsoft-finops-toolkit' } - $entry.source | Should -Be $marketplaces[$marketplace] + $entry.source | Should -Be './src/templates/agent-plugin' } } + + It 'Enables strict mode explicitly for the Claude plugin' { + $json = Get-Content (Join-Path $script:RepoRoot '.claude-plugin/marketplace.json') -Raw | ConvertFrom-Json + $entry = $json.plugins | Where-Object { $_.name -eq 'microsoft-finops-toolkit' } + + $entry.strict | Should -BeTrue + } } \ No newline at end of file diff --git a/src/templates/agent-plugin/README.md b/src/templates/agent-plugin/README.md index 04b3bce6c..466175e66 100644 --- a/src/templates/agent-plugin/README.md +++ b/src/templates/agent-plugin/README.md @@ -4,9 +4,9 @@ This plugin is the canonical source for the FinOps Toolkit agent plugins. ## Distribution and runtime conventions -- `plugins/microsoft-finops-toolkit` is the marketplace source used by both marketplace manifests. +- Both marketplace manifests point directly to this directory to avoid relying on repository symlink support. - `.plugin` is the repository-level discovery pointer for the plugin manifest. -- GitHub Copilot CLI declares agents and `.mcp.json` in `plugin.json`; Claude Code discovers those assets from the plugin root and declares its Claude-specific output style in `.claude-plugin/plugin.json`. +- GitHub Copilot CLI declares agents and `.mcp.json` in `plugin.json`; Claude Code discovers agents and `.mcp.json` from the plugin root and declares its Claude-specific output style in `.claude-plugin/plugin.json`. - The Azure MCP server uses `@azure/mcp@latest` so the plugin receives compatible Azure MCP updates without a separate plugin release. It is limited to the Kusto namespace and read-only operations. For setup and usage: diff --git a/src/templates/agent-plugin/agents/azure-capacity-manager.agent.md b/src/templates/agent-plugin/agents/azure-capacity-manager.md similarity index 100% rename from src/templates/agent-plugin/agents/azure-capacity-manager.agent.md rename to src/templates/agent-plugin/agents/azure-capacity-manager.md diff --git a/src/templates/agent-plugin/agents/chief-financial-officer.agent.md b/src/templates/agent-plugin/agents/chief-financial-officer.md similarity index 100% rename from src/templates/agent-plugin/agents/chief-financial-officer.agent.md rename to src/templates/agent-plugin/agents/chief-financial-officer.md diff --git a/src/templates/agent-plugin/agents/finops-practitioner.agent.md b/src/templates/agent-plugin/agents/finops-practitioner.md similarity index 100% rename from src/templates/agent-plugin/agents/finops-practitioner.agent.md rename to src/templates/agent-plugin/agents/finops-practitioner.md diff --git a/src/templates/agent-plugin/agents/ftk-database-query.agent.md b/src/templates/agent-plugin/agents/ftk-database-query.md similarity index 100% rename from src/templates/agent-plugin/agents/ftk-database-query.agent.md rename to src/templates/agent-plugin/agents/ftk-database-query.md diff --git a/src/templates/agent-plugin/agents/ftk-hubs-agent.agent.md b/src/templates/agent-plugin/agents/ftk-hubs-agent.md similarity index 100% rename from src/templates/agent-plugin/agents/ftk-hubs-agent.agent.md rename to src/templates/agent-plugin/agents/ftk-hubs-agent.md