From a09a7dc005221a8afb18cea8f0809c736f38dc11 Mon Sep 17 00:00:00 2001 From: Jon Gallant <2163001+jongio@users.noreply.github.com> Date: Wed, 3 Jun 2026 15:54:58 -0700 Subject: [PATCH 1/6] feat(azure-cost): add App Service optimization guide - Add services/appservice/azure-app-service.md with plan rightsizing, idle slot detection, dev/test pricing, and Resource Graph queries - Consolidate service routing in workflow.md into compact table format - Add 2 vally eval stimuli for App Service cost routing - Trim storage intro and SKILL.md to stay within token budgets Partial progress on #2524 --- evals/azure-cost/eval.yaml | 42 ++++++++++ plugin/skills/azure-cost/SKILL.md | 7 +- .../services/appservice/azure-app-service.md | 83 +++++++++++++++++++ .../services/storage/azure-storage.md | 2 +- .../azure-cost/cost-optimization/workflow.md | 23 ++--- 5 files changed, 140 insertions(+), 17 deletions(-) create mode 100644 plugin/skills/azure-cost/cost-optimization/services/appservice/azure-app-service.md diff --git a/evals/azure-cost/eval.yaml b/evals/azure-cost/eval.yaml index 39a64fc2f..04a501e8c 100644 --- a/evals/azure-cost/eval.yaml +++ b/evals/azure-cost/eval.yaml @@ -371,6 +371,48 @@ stimuli: config: pattern: "(?i)fatal error|unhandled exception|stack trace" + # ═══════════════════════════════════════════ + # App Service Cost Optimization routing prompts + # ═══════════════════════════════════════════ + + # ── appservice-idle-slots-prompt ── + # Assertions: softCheckSkill + isSkillInvoked (invocation rate ≥ 80%) + - name: "App Service idle deployment slots cost" + prompt: "I have deployment slots on my App Service that aren't being used, how much are they costing me?" + tags: + type: integration + tier: full + cost: llm + area: routing + earlyTerminate: '[{"type":"skill-call","skill":"azure-cost"},{"type":"tool-call-count","count":3}]' + graders: + - type: skill-invocation + config: + required: + - azure-cost + - type: output-not-matches + config: + pattern: "(?i)fatal error|unhandled exception|stack trace" + + # ── appservice-plan-downgrade-prompt ── + # Assertions: softCheckSkill + isSkillInvoked (invocation rate ≥ 80%) + - name: "App Service plan downgrade savings" + prompt: "Can I save money by downgrading my Premium App Service plans in dev/test to a cheaper tier?" + tags: + type: integration + tier: full + cost: llm + area: routing + earlyTerminate: '[{"type":"skill-call","skill":"azure-cost"},{"type":"tool-call-count","count":3}]' + graders: + - type: skill-invocation + config: + required: + - azure-cost + - type: output-not-matches + config: + pattern: "(?i)fatal error|unhandled exception|stack trace" + # ═══════════════════════════════════════════ # Response quality tests # ═══════════════════════════════════════════ diff --git a/plugin/skills/azure-cost/SKILL.md b/plugin/skills/azure-cost/SKILL.md index 4d287876f..d1be9c476 100644 --- a/plugin/skills/azure-cost/SKILL.md +++ b/plugin/skills/azure-cost/SKILL.md @@ -32,14 +32,11 @@ Query historical costs, forecast future spending, optimize to reduce waste. - Subscription: `/subscriptions/` - Resource Group: `/subscriptions//resourceGroups/` - Management Group: `/providers/Microsoft.Management/managementGroups/` -- Billing Account: `/providers/Microsoft.Billing/billingAccounts/` -## Service-Specific Optimization +## Service Optimization Guides -- [Redis](cost-optimization/services/redis/azure-cache-for-redis.md) -- [Storage](cost-optimization/services/storage/azure-storage.md) +[Redis](cost-optimization/services/redis/azure-cache-for-redis.md) | [Storage](cost-optimization/services/storage/azure-storage.md) | [App Service](cost-optimization/services/appservice/azure-app-service.md) ## References - [MCP Tools, Best Practices, Safety](references/tools-and-best-practices.md) -- [SDK: Redis .NET](cost-optimization/sdk/azure-resource-manager-redis-dotnet.md) diff --git a/plugin/skills/azure-cost/cost-optimization/services/appservice/azure-app-service.md b/plugin/skills/azure-cost/cost-optimization/services/appservice/azure-app-service.md new file mode 100644 index 000000000..5d34dfaac --- /dev/null +++ b/plugin/skills/azure-cost/cost-optimization/services/appservice/azure-app-service.md @@ -0,0 +1,83 @@ +## Azure App Service Cost Optimization + +Reference guide for reducing App Service costs through plan rightsizing, idle slot cleanup, and dev/test pricing. + +## Cost Optimization Rules + +| Priority | Rule | Detection Logic | Recommendation | Avg Savings | +|----------|------|----------------|----------------|-------------| +| 🔴 Critical | Stopped App on Paid Plan | `state == 'Stopped'` AND `sku.tier != 'Free/Shared'` | Delete or move to Free tier (stopped apps still incur plan cost) | $50-500/mo | +| 🔴 Critical | Empty App Service Plan | Plan has zero apps deployed | Delete the plan | $50-400/mo | +| 🟠 High | Premium in Non-Production | `sku.tier in ['PremiumV2','PremiumV3']` AND `tags.environment in ['dev','test','staging']` | Downgrade to Basic or Standard | $100-600/mo | +| 🟠 High | Idle Deployment Slots | Non-production slots with zero traffic for 14+ days | Delete unused slots (each slot = separate app instance cost) | $50-300/mo | +| 🟠 High | Over-Provisioned Plan | CPU avg <20% AND memory avg <30% over 14 days | Scale down SKU or reduce instance count | $50-400/mo | +| 🟡 Medium | No Auto-Scale Rules | Production plan with fixed instance count >2 | Add auto-scale rules to scale in during low traffic | $30-200/mo | +| 🟡 Medium | Missing Dev/Test Pricing | Dev/test workloads on regular pricing | Enable Dev/Test pricing via subscription offer | 30-55% savings | +| 🟡 Medium | Always On for Non-Production | `alwaysOn == true` on dev/test apps | Disable Always On (apps cold-start on first request) | Reduced idle cost | +| 🟢 Low | Untagged App Service | Missing `environment`, `owner`, or `costCenter` tags | Apply tags for cost allocation | N/A | +| 🟢 Low | Old Deployment Slots | Slots older than 90 days not used for blue-green | Review if still needed | Variable | + +## Plan Tier Decision Matrix + +| Workload | Recommended Tier | Key Features | +|----------|-----------------|--------------| +| Dev/test, prototypes | Free / Basic B1 | No SLA, limited scale | +| Low-traffic production | Standard S1 | Auto-scale, slots, backups | +| High-traffic production | Premium P1v3 | Better perf, more slots, VNET | +| Isolated compliance | Isolated I1v2 | Private environment, max scale | + +## Resource Graph Queries + +**Find stopped apps on paid plans:** + +```kql +Resources +| where type =~ 'microsoft.web/sites' +| where properties.state =~ 'Stopped' +| where isnotempty(properties.serverFarmId) +| project name, resourceGroup, kind, state=properties.state +``` + +**Find empty App Service Plans:** + +```kql +Resources +| where type =~ 'microsoft.web/serverfarms' +| where properties.numberOfSites == 0 +| project name, resourceGroup, sku=sku.name, location +``` + +**Find Premium plans in non-production:** + +```kql +Resources +| where type =~ 'microsoft.web/serverfarms' +| where sku.tier in~ ('PremiumV2', 'PremiumV3', 'Premium') +| where tags.environment in~ ('dev', 'test', 'staging', 'sandbox') +| project name, resourceGroup, sku=sku.name, tier=sku.tier, tags +``` + +## Tools & Commands + +**MCP Tool:** `azure__appservice` for listing and managing App Service resources + +**Azure CLI:** +- `az appservice plan list --resource-group ` - List plans +- `az appservice plan show --name --resource-group ` - Plan details +- `az webapp list --resource-group ` - List web apps +- `az webapp show --name --resource-group ` - App details +- `az webapp deployment slot list --name --resource-group ` - List slots +- `az monitor metrics list --resource --metric CpuPercentage --interval PT1H` - CPU utilization +- `az monitor metrics list --resource --metric MemoryPercentage --interval PT1H` - Memory utilization + +## Pricing Quick Reference + +Approximate monthly costs (Linux, East US): +- **Free F1**: $0 (60 min CPU/day, 1 GB RAM) +- **Basic B1**: ~$13/mo (1 core, 1.75 GB) +- **Standard S1**: ~$69/mo (1 core, 1.75 GB, auto-scale, slots) +- **Premium P1v3**: ~$138/mo (2 cores, 8 GB, better perf) + +Each deployment slot runs as a separate instance at full plan cost. Windows plans cost ~30% more than Linux. + +Always validate from [official pricing](https://azure.microsoft.com/pricing/details/app-service/). diff --git a/plugin/skills/azure-cost/cost-optimization/services/storage/azure-storage.md b/plugin/skills/azure-cost/cost-optimization/services/storage/azure-storage.md index 552617b9f..8bd62e0fd 100644 --- a/plugin/skills/azure-cost/cost-optimization/services/storage/azure-storage.md +++ b/plugin/skills/azure-cost/cost-optimization/services/storage/azure-storage.md @@ -1,6 +1,6 @@ ## Azure Storage Cost Optimization -Reference guide for identifying cost savings opportunities in Azure Storage accounts through tier analysis, lifecycle policies, and orphaned resource detection. +Identify cost savings in Azure Storage through tier analysis, lifecycle policies, and orphaned resource detection. ## Subscription Input Options diff --git a/plugin/skills/azure-cost/cost-optimization/workflow.md b/plugin/skills/azure-cost/cost-optimization/workflow.md index 8be64ff02..269f3bff6 100644 --- a/plugin/skills/azure-cost/cost-optimization/workflow.md +++ b/plugin/skills/azure-cost/cost-optimization/workflow.md @@ -57,13 +57,14 @@ azure__get_azure_bestpractices({ Wait for user response before proceeding. -## Step 1.7: Storage-Specific Analysis (Conditional) +## Steps 1.7–1.75: Service-Specific Analysis (Conditional) -**If the user requests Storage cost optimization**, load: [Azure Storage Cost Optimization](./services/storage/azure-storage.md) +For service-focused requests, load the relevant guide and follow it. For general optimization, skip to Step 2. -**Triggers:** "storage account cost", "blob storage savings", "LRS/GRS/ZRS downgrade", "storage lifecycle savings", "reduce storage spending". - -For Storage-only requests, follow the Storage reference. For general optimization that includes storage, continue to Step 2. +| Triggers | Reference | +|----------|-----------| +| "storage account cost", "blob savings", "LRS/GRS downgrade", "storage lifecycle savings" | [Storage](./services/storage/azure-storage.md) | +| "app service cost", "plan savings", "web app spending", "idle slots", "overprovisioned plan" | [App Service](./services/appservice/azure-app-service.md) | ## Step 1.8: AKS-Specific Analysis (Conditional) @@ -75,14 +76,14 @@ For Storage-only requests, follow the Storage reference. For general optimizatio - User reports a cost spike, unusual cluster utilization, or wants budget alerts **Tool Selection:** -- **Prefer MCP first**: Use `azure__aks` for AKS operations (list clusters, get node pools, inspect configuration) — it provides richer metadata and is consistent with AKS skill conventions in this repo -- **Fall back to CLI**: Use `az aks` and `kubectl` only when the specific operation cannot be performed via the MCP surface +- **Prefer MCP first**: Use `azure__aks` for AKS operations (list clusters, get node pools, inspect configuration) +- **Fall back to CLI**: Use `az aks` and `kubectl` only when MCP doesn't cover the operation -**Reference files (load only what is needed for the request):** -- [Cost Analysis Add-on](./azure-aks-cost-addon.md) — enable namespace-level cost visibility -- [Anomaly Investigation](./azure-aks-anomalies.md) — cost spikes, scaling events, budget alerts +**Reference files:** +- [Cost Analysis Add-on](./azure-aks-cost-addon.md) +- [Anomaly Investigation](./azure-aks-anomalies.md) -> **Note**: For general subscription-wide cost optimization (including AKS resource groups), continue with Step 2. For AKS-focused analysis, follow the instructions in the relevant reference file above. +For general optimization (including AKS resource groups), continue to Step 2. ## Step 1.9: Choose Analysis Scope (for AKS-specific analysis) From a57366eaba6f5298c4f6ca81dbd8443cc81881c7 Mon Sep 17 00:00:00 2001 From: Jon Gallant <2163001+jongio@users.noreply.github.com> Date: Wed, 3 Jun 2026 17:37:36 -0700 Subject: [PATCH 2/6] fix: correct App Service billing model and tool guidance - Slots share plan workers (no per-slot charge), can drive scale-out - Fix ARG query heading to match actual query scope - Replace azure__appservice MCP reference with ARG/CLI for discovery --- package-lock.json | 2 ++ .../services/appservice/azure-app-service.md | 8 ++--- scripts/package-lock.json | 32 ++++++------------- tests/package-lock.json | 13 ++++++++ 4 files changed, 28 insertions(+), 27 deletions(-) diff --git a/package-lock.json b/package-lock.json index a79f19ebc..8d178eb00 100644 --- a/package-lock.json +++ b/package-lock.json @@ -160,6 +160,7 @@ "integrity": "sha512-AOQwYUNolgy3VosiRqXrACUXTN8nJUtPl7FJXMqZVyxiiCLhQuG3jXKvCS1ALr+Y2OmZhzzLVlYPEqJaiqkaJQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } @@ -2027,6 +2028,7 @@ "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/plugin/skills/azure-cost/cost-optimization/services/appservice/azure-app-service.md b/plugin/skills/azure-cost/cost-optimization/services/appservice/azure-app-service.md index 5d34dfaac..a7240b0a4 100644 --- a/plugin/skills/azure-cost/cost-optimization/services/appservice/azure-app-service.md +++ b/plugin/skills/azure-cost/cost-optimization/services/appservice/azure-app-service.md @@ -9,7 +9,7 @@ Reference guide for reducing App Service costs through plan rightsizing, idle sl | 🔴 Critical | Stopped App on Paid Plan | `state == 'Stopped'` AND `sku.tier != 'Free/Shared'` | Delete or move to Free tier (stopped apps still incur plan cost) | $50-500/mo | | 🔴 Critical | Empty App Service Plan | Plan has zero apps deployed | Delete the plan | $50-400/mo | | 🟠 High | Premium in Non-Production | `sku.tier in ['PremiumV2','PremiumV3']` AND `tags.environment in ['dev','test','staging']` | Downgrade to Basic or Standard | $100-600/mo | -| 🟠 High | Idle Deployment Slots | Non-production slots with zero traffic for 14+ days | Delete unused slots (each slot = separate app instance cost) | $50-300/mo | +| 🟠 High | Idle Deployment Slots | Non-production slots with zero traffic for 14+ days | Delete unused slots (slots share plan workers but increase utilization, driving scale-out) | $30-150/mo | | 🟠 High | Over-Provisioned Plan | CPU avg <20% AND memory avg <30% over 14 days | Scale down SKU or reduce instance count | $50-400/mo | | 🟡 Medium | No Auto-Scale Rules | Production plan with fixed instance count >2 | Add auto-scale rules to scale in during low traffic | $30-200/mo | | 🟡 Medium | Missing Dev/Test Pricing | Dev/test workloads on regular pricing | Enable Dev/Test pricing via subscription offer | 30-55% savings | @@ -28,7 +28,7 @@ Reference guide for reducing App Service costs through plan rightsizing, idle sl ## Resource Graph Queries -**Find stopped apps on paid plans:** +**Find stopped apps (review for deletion or plan downgrade):** ```kql Resources @@ -59,7 +59,7 @@ Resources ## Tools & Commands -**MCP Tool:** `azure__appservice` for listing and managing App Service resources +**Discovery:** Use Azure Resource Graph or `az` CLI for listing App Service resources (the `azure__appservice` MCP tool has limited list support). **Azure CLI:** - `az appservice plan list --resource-group ` - List plans @@ -78,6 +78,6 @@ Approximate monthly costs (Linux, East US): - **Standard S1**: ~$69/mo (1 core, 1.75 GB, auto-scale, slots) - **Premium P1v3**: ~$138/mo (2 cores, 8 GB, better perf) -Each deployment slot runs as a separate instance at full plan cost. Windows plans cost ~30% more than Linux. +Deployment slots share the plan's compute workers (no separate per-slot charge), but extra slots increase resource utilization and can trigger scale-out. Windows plans cost ~30% more than Linux. Always validate from [official pricing](https://azure.microsoft.com/pricing/details/app-service/). diff --git a/scripts/package-lock.json b/scripts/package-lock.json index bd594bc0d..d0057b3ed 100644 --- a/scripts/package-lock.json +++ b/scripts/package-lock.json @@ -84,29 +84,6 @@ "node": ">=18" } }, - "node_modules/@emnapi/core": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz", - "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.0", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz", - "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", @@ -1159,6 +1136,7 @@ "integrity": "sha512-AOQwYUNolgy3VosiRqXrACUXTN8nJUtPl7FJXMqZVyxiiCLhQuG3jXKvCS1ALr+Y2OmZhzzLVlYPEqJaiqkaJQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } @@ -1208,6 +1186,7 @@ "integrity": "sha512-TI1XGwKbDpo9tRW8UDIXCOeLk55qe9ZFGs8MTKU6/M08HWTw52DD/IYhfQtOEhEdPhLMT26Ka/x7p70nd3dzDg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.59.0", "@typescript-eslint/types": "8.59.0", @@ -1543,6 +1522,7 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1780,6 +1760,7 @@ "integrity": "sha512-loXy6bWOoP3EP6JA7jo6p5jMpBJmHmsNZM5SFRHLdh1MGOPurMnNBj4ZlAbaqUAaQWbCr7jHV4P7gzAyryZWkQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", @@ -2915,6 +2896,7 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -3280,6 +3262,7 @@ "integrity": "sha512-TvncJykhxAzFCk0VQZKBTClall4Pm7qXDSodb6uxi8QFa8X8mT6ABjxxsQ2opDRYxG7AzcRWXaFtruz5HJKuWg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "~0.28.0" }, @@ -3312,6 +3295,7 @@ "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3367,6 +3351,7 @@ "integrity": "sha512-P1PbweD+2/udplnThz3btF4cf6AgPky7kk23RtHUkJIU5BIxwPprhRGmOAHs6FTI7UiGbTNrgNP6jSYD6JaRnw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", @@ -3445,6 +3430,7 @@ "integrity": "sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/expect": "4.1.2", "@vitest/mocker": "4.1.2", diff --git a/tests/package-lock.json b/tests/package-lock.json index 62d324611..7308a5c88 100644 --- a/tests/package-lock.json +++ b/tests/package-lock.json @@ -243,6 +243,7 @@ "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", @@ -2106,6 +2107,7 @@ "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.19.0" } @@ -2140,6 +2142,7 @@ "integrity": "sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.58.0", @@ -2179,6 +2182,7 @@ "integrity": "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/types": "8.58.0", @@ -2380,6 +2384,7 @@ "integrity": "sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.58.0", @@ -2713,6 +2718,7 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3061,6 +3067,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -3657,6 +3664,7 @@ "integrity": "sha512-+L0vBFYGIpSNIt/KWTpFonPrqYvgKw1eUI5Vn7mEogrQcWtWYtNQ7dNqC+px/J0idT3BAkiWrhfS7k+Tum8TUA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", @@ -3773,6 +3781,7 @@ "integrity": "sha512-rM9K8UBHcWKpzQzStn1YRN2T5NvdeIfSVoKu/lKF41znQXHAUcBbYXe5wd6GNjZjTrP7viQ49n1D83x/2gYgIw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@package-json/types": "^0.0.12", "@typescript-eslint/types": "^8.56.0", @@ -4497,6 +4506,7 @@ "integrity": "sha512-7fvVPbB92zNRsQke+uiRGwtTuef0tB2Dg4hWxYfFNvkQhIltWoyi0ONReM5LWA+jJWS3nfT5lTq+qbsIpX0IQw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=16.9.0" } @@ -4910,6 +4920,7 @@ "integrity": "sha512-AkXIIFcaazymvey2i/+F94XRnM6TsVLZDhBMLsd1Sf/W0wzsvvpjeyUrCZD6HGG4SDYPgDJDBKeiJTBb10WzMg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/core": "30.3.0", "@jest/types": "30.3.0", @@ -7092,6 +7103,7 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -7192,6 +7204,7 @@ "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" From 60e6b9323ceb597bee7167ed6d10284e0298021a Mon Sep 17 00:00:00 2001 From: Jon Gallant <2163001+jongio@users.noreply.github.com> Date: Wed, 3 Jun 2026 19:25:26 -0700 Subject: [PATCH 3/6] fix: restore lockfiles to match main (npm ci compat) --- package-lock.json | 2 -- scripts/package-lock.json | 32 +++++++++++++++++++++++--------- tests/package-lock.json | 13 ------------- 3 files changed, 23 insertions(+), 24 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8d178eb00..a79f19ebc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -160,7 +160,6 @@ "integrity": "sha512-AOQwYUNolgy3VosiRqXrACUXTN8nJUtPl7FJXMqZVyxiiCLhQuG3jXKvCS1ALr+Y2OmZhzzLVlYPEqJaiqkaJQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } @@ -2028,7 +2027,6 @@ "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/scripts/package-lock.json b/scripts/package-lock.json index d0057b3ed..bd594bc0d 100644 --- a/scripts/package-lock.json +++ b/scripts/package-lock.json @@ -84,6 +84,29 @@ "node": ">=18" } }, + "node_modules/@emnapi/core": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz", + "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz", + "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", @@ -1136,7 +1159,6 @@ "integrity": "sha512-AOQwYUNolgy3VosiRqXrACUXTN8nJUtPl7FJXMqZVyxiiCLhQuG3jXKvCS1ALr+Y2OmZhzzLVlYPEqJaiqkaJQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } @@ -1186,7 +1208,6 @@ "integrity": "sha512-TI1XGwKbDpo9tRW8UDIXCOeLk55qe9ZFGs8MTKU6/M08HWTw52DD/IYhfQtOEhEdPhLMT26Ka/x7p70nd3dzDg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.59.0", "@typescript-eslint/types": "8.59.0", @@ -1522,7 +1543,6 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1760,7 +1780,6 @@ "integrity": "sha512-loXy6bWOoP3EP6JA7jo6p5jMpBJmHmsNZM5SFRHLdh1MGOPurMnNBj4ZlAbaqUAaQWbCr7jHV4P7gzAyryZWkQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", @@ -2896,7 +2915,6 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -3262,7 +3280,6 @@ "integrity": "sha512-TvncJykhxAzFCk0VQZKBTClall4Pm7qXDSodb6uxi8QFa8X8mT6ABjxxsQ2opDRYxG7AzcRWXaFtruz5HJKuWg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "~0.28.0" }, @@ -3295,7 +3312,6 @@ "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3351,7 +3367,6 @@ "integrity": "sha512-P1PbweD+2/udplnThz3btF4cf6AgPky7kk23RtHUkJIU5BIxwPprhRGmOAHs6FTI7UiGbTNrgNP6jSYD6JaRnw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", @@ -3430,7 +3445,6 @@ "integrity": "sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/expect": "4.1.2", "@vitest/mocker": "4.1.2", diff --git a/tests/package-lock.json b/tests/package-lock.json index 7308a5c88..62d324611 100644 --- a/tests/package-lock.json +++ b/tests/package-lock.json @@ -243,7 +243,6 @@ "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", @@ -2107,7 +2106,6 @@ "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.19.0" } @@ -2142,7 +2140,6 @@ "integrity": "sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.58.0", @@ -2182,7 +2179,6 @@ "integrity": "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/types": "8.58.0", @@ -2384,7 +2380,6 @@ "integrity": "sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.58.0", @@ -2718,7 +2713,6 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3067,7 +3061,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -3664,7 +3657,6 @@ "integrity": "sha512-+L0vBFYGIpSNIt/KWTpFonPrqYvgKw1eUI5Vn7mEogrQcWtWYtNQ7dNqC+px/J0idT3BAkiWrhfS7k+Tum8TUA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", @@ -3781,7 +3773,6 @@ "integrity": "sha512-rM9K8UBHcWKpzQzStn1YRN2T5NvdeIfSVoKu/lKF41znQXHAUcBbYXe5wd6GNjZjTrP7viQ49n1D83x/2gYgIw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@package-json/types": "^0.0.12", "@typescript-eslint/types": "^8.56.0", @@ -4506,7 +4497,6 @@ "integrity": "sha512-7fvVPbB92zNRsQke+uiRGwtTuef0tB2Dg4hWxYfFNvkQhIltWoyi0ONReM5LWA+jJWS3nfT5lTq+qbsIpX0IQw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=16.9.0" } @@ -4920,7 +4910,6 @@ "integrity": "sha512-AkXIIFcaazymvey2i/+F94XRnM6TsVLZDhBMLsd1Sf/W0wzsvvpjeyUrCZD6HGG4SDYPgDJDBKeiJTBb10WzMg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "30.3.0", "@jest/types": "30.3.0", @@ -7103,7 +7092,6 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -7204,7 +7192,6 @@ "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" From ccfa7995e9cdd25ee8994d64803af756af4882b5 Mon Sep 17 00:00:00 2001 From: Jon Gallant <2163001+jongio@users.noreply.github.com> Date: Wed, 3 Jun 2026 19:54:34 -0700 Subject: [PATCH 4/6] fix: address Copilot review round 2 - Restore Billing Account scope pattern in SKILL.md - Convert pipe-separated links to bullet list - Fix workflow heading (Step 1.7 not Steps 1.7-1.75) - Add Subscription Input Options to App Service guide - Fix tier detection logic (Free and Shared are separate values) - Clarify Dev/Test pricing is subscription-level --- plugin/skills/azure-cost/SKILL.md | 5 ++++- .../services/appservice/azure-app-service.md | 10 +++++++--- plugin/skills/azure-cost/cost-optimization/workflow.md | 2 +- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/plugin/skills/azure-cost/SKILL.md b/plugin/skills/azure-cost/SKILL.md index d1be9c476..47bfecf69 100644 --- a/plugin/skills/azure-cost/SKILL.md +++ b/plugin/skills/azure-cost/SKILL.md @@ -32,10 +32,13 @@ Query historical costs, forecast future spending, optimize to reduce waste. - Subscription: `/subscriptions/` - Resource Group: `/subscriptions//resourceGroups/` - Management Group: `/providers/Microsoft.Management/managementGroups/` +- Billing Account: `/providers/Microsoft.Billing/billingAccounts/` ## Service Optimization Guides -[Redis](cost-optimization/services/redis/azure-cache-for-redis.md) | [Storage](cost-optimization/services/storage/azure-storage.md) | [App Service](cost-optimization/services/appservice/azure-app-service.md) +- [Redis](cost-optimization/services/redis/azure-cache-for-redis.md) +- [Storage](cost-optimization/services/storage/azure-storage.md) +- [App Service](cost-optimization/services/appservice/azure-app-service.md) ## References diff --git a/plugin/skills/azure-cost/cost-optimization/services/appservice/azure-app-service.md b/plugin/skills/azure-cost/cost-optimization/services/appservice/azure-app-service.md index a7240b0a4..e0063a9cc 100644 --- a/plugin/skills/azure-cost/cost-optimization/services/appservice/azure-app-service.md +++ b/plugin/skills/azure-cost/cost-optimization/services/appservice/azure-app-service.md @@ -1,18 +1,22 @@ ## Azure App Service Cost Optimization -Reference guide for reducing App Service costs through plan rightsizing, idle slot cleanup, and dev/test pricing. +Reduce App Service costs through plan rightsizing, idle slot cleanup, and dev/test pricing. + +## Subscription Input Options + +Accept any of these to scope the analysis: Subscription ID, Subscription Name, Resource Group, or "All my subscriptions". ## Cost Optimization Rules | Priority | Rule | Detection Logic | Recommendation | Avg Savings | |----------|------|----------------|----------------|-------------| -| 🔴 Critical | Stopped App on Paid Plan | `state == 'Stopped'` AND `sku.tier != 'Free/Shared'` | Delete or move to Free tier (stopped apps still incur plan cost) | $50-500/mo | +| 🔴 Critical | Stopped App on Paid Plan | `state == 'Stopped'` AND `sku.tier not in ['Free', 'Shared']` | Delete or move to Free tier (stopped apps still incur plan cost) | $50-500/mo | | 🔴 Critical | Empty App Service Plan | Plan has zero apps deployed | Delete the plan | $50-400/mo | | 🟠 High | Premium in Non-Production | `sku.tier in ['PremiumV2','PremiumV3']` AND `tags.environment in ['dev','test','staging']` | Downgrade to Basic or Standard | $100-600/mo | | 🟠 High | Idle Deployment Slots | Non-production slots with zero traffic for 14+ days | Delete unused slots (slots share plan workers but increase utilization, driving scale-out) | $30-150/mo | | 🟠 High | Over-Provisioned Plan | CPU avg <20% AND memory avg <30% over 14 days | Scale down SKU or reduce instance count | $50-400/mo | | 🟡 Medium | No Auto-Scale Rules | Production plan with fixed instance count >2 | Add auto-scale rules to scale in during low traffic | $30-200/mo | -| 🟡 Medium | Missing Dev/Test Pricing | Dev/test workloads on regular pricing | Enable Dev/Test pricing via subscription offer | 30-55% savings | +| 🟡 Medium | Missing Dev/Test Pricing | Dev/test workloads on regular pricing | Apply Azure Dev/Test subscription offer (subscription-level, not plan-level) | 30-55% savings | | 🟡 Medium | Always On for Non-Production | `alwaysOn == true` on dev/test apps | Disable Always On (apps cold-start on first request) | Reduced idle cost | | 🟢 Low | Untagged App Service | Missing `environment`, `owner`, or `costCenter` tags | Apply tags for cost allocation | N/A | | 🟢 Low | Old Deployment Slots | Slots older than 90 days not used for blue-green | Review if still needed | Variable | diff --git a/plugin/skills/azure-cost/cost-optimization/workflow.md b/plugin/skills/azure-cost/cost-optimization/workflow.md index 269f3bff6..faf4897bf 100644 --- a/plugin/skills/azure-cost/cost-optimization/workflow.md +++ b/plugin/skills/azure-cost/cost-optimization/workflow.md @@ -57,7 +57,7 @@ azure__get_azure_bestpractices({ Wait for user response before proceeding. -## Steps 1.7–1.75: Service-Specific Analysis (Conditional) +## Step 1.7: Service-Specific Analysis (Conditional) For service-focused requests, load the relevant guide and follow it. For general optimization, skip to Step 2. From e94418a6c3d8f7dc65ae1cd40e3cb647c067b0df Mon Sep 17 00:00:00 2001 From: Jon Gallant <2163001+jongio@users.noreply.github.com> Date: Wed, 10 Jun 2026 07:59:07 -0700 Subject: [PATCH 5/6] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../cost-optimization/services/appservice/azure-app-service.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/skills/azure-cost/cost-optimization/services/appservice/azure-app-service.md b/plugin/skills/azure-cost/cost-optimization/services/appservice/azure-app-service.md index e0063a9cc..1bb9b1adf 100644 --- a/plugin/skills/azure-cost/cost-optimization/services/appservice/azure-app-service.md +++ b/plugin/skills/azure-cost/cost-optimization/services/appservice/azure-app-service.md @@ -10,7 +10,7 @@ Accept any of these to scope the analysis: Subscription ID, Subscription Name, R | Priority | Rule | Detection Logic | Recommendation | Avg Savings | |----------|------|----------------|----------------|-------------| -| 🔴 Critical | Stopped App on Paid Plan | `state == 'Stopped'` AND `sku.tier not in ['Free', 'Shared']` | Delete or move to Free tier (stopped apps still incur plan cost) | $50-500/mo | +| 🔴 Critical | Stopped App on Paid Plan | Site `properties.state == 'Stopped'` and associated plan tier is not Free/Shared | Delete the app or move it to a Free/Shared plan; if it’s the only app on the plan, delete/scale down the plan | $50-500/mo | | 🔴 Critical | Empty App Service Plan | Plan has zero apps deployed | Delete the plan | $50-400/mo | | 🟠 High | Premium in Non-Production | `sku.tier in ['PremiumV2','PremiumV3']` AND `tags.environment in ['dev','test','staging']` | Downgrade to Basic or Standard | $100-600/mo | | 🟠 High | Idle Deployment Slots | Non-production slots with zero traffic for 14+ days | Delete unused slots (slots share plan workers but increase utilization, driving scale-out) | $30-150/mo | From 59fab1d731c4fe7da4e6d0b9d3ba94a7e981d9c7 Mon Sep 17 00:00:00 2001 From: Jon Gallant <2163001+jongio@users.noreply.github.com> Date: Thu, 25 Jun 2026 18:25:46 -0700 Subject: [PATCH 6/6] fix(azure-cost): address App Service guide review feedback - Stopped-app KQL now joins serverfarms and filters on plan tier (Free/Shared/Dynamic excluded) so the query matches the rule it documents - Align Premium non-prod detection logic with the KQL query (Premium/PremiumV2/PremiumV3 + sandbox env) - Remove 'Always On' from cost rules: it is a free per-app toggle that does not affect plan billing - Split Free vs Basic in the tier matrix (Basic B1 has a 99.95% SLA) - Add App Service trigger terms to the azure-cost skill description for routing - Delete orphaned Redis .NET SDK reference (link removed earlier in this PR) - Add bill-breakdown reminder to the App Service guide Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- plugin/skills/azure-cost/SKILL.md | 2 +- .../azure-resource-manager-redis-dotnet.md | 31 ------------------- .../services/appservice/azure-app-service.md | 22 ++++++++----- 3 files changed, 16 insertions(+), 39 deletions(-) delete mode 100644 plugin/skills/azure-cost/cost-optimization/sdk/azure-resource-manager-redis-dotnet.md diff --git a/plugin/skills/azure-cost/SKILL.md b/plugin/skills/azure-cost/SKILL.md index 47bfecf69..f369bd3e3 100644 --- a/plugin/skills/azure-cost/SKILL.md +++ b/plugin/skills/azure-cost/SKILL.md @@ -1,6 +1,6 @@ --- name: azure-cost -description: "Azure cost management: query costs, forecast spending, optimize to reduce waste. WHEN: \"Azure costs\", \"Azure bill\", \"cost breakdown\", \"how much am I spending\", \"forecast spending\", \"optimize costs\", \"reduce spending\", \"orphaned resources\", \"rightsize VMs\", \"cost spike\", \"reduce storage costs\", \"AKS cost\". DO NOT USE FOR: deploying resources, provisioning, diagnostics, or security audits." +description: "Azure cost management: query costs, forecast spending, optimize to reduce waste. WHEN: \"Azure costs\", \"Azure bill\", \"cost breakdown\", \"how much am I spending\", \"forecast spending\", \"optimize costs\", \"reduce spending\", \"orphaned resources\", \"rightsize VMs\", \"cost spike\", \"reduce storage costs\", \"App Service cost\", \"web app spending\", \"App Service plan savings\", \"deployment slots\", \"AKS cost\". DO NOT USE FOR: deploying resources, provisioning, diagnostics, or security audits." license: MIT metadata: author: Microsoft diff --git a/plugin/skills/azure-cost/cost-optimization/sdk/azure-resource-manager-redis-dotnet.md b/plugin/skills/azure-cost/cost-optimization/sdk/azure-resource-manager-redis-dotnet.md deleted file mode 100644 index e5ce86e1e..000000000 --- a/plugin/skills/azure-cost/cost-optimization/sdk/azure-resource-manager-redis-dotnet.md +++ /dev/null @@ -1,31 +0,0 @@ -# Redis Management — .NET SDK Quick Reference - -> Condensed from **azure-resource-manager-redis-dotnet**. Full patterns -> (cache creation, firewall rules, access keys, geo-replication, patching) -> in the **azure-resource-manager-redis-dotnet** plugin skill if installed. - -## Install -dotnet add package Azure.ResourceManager.Redis -dotnet add package Azure.Identity - -## Quick Start - -> **Auth:** `DefaultAzureCredential` is for local development. See [auth-best-practices.md](../auth-best-practices.md) for production patterns. - -```csharp -using Azure.ResourceManager; -using Azure.Identity; -var armClient = new ArmClient(new DefaultAzureCredential()); -``` - -## Best Practices -- Use `WaitUntil.Completed` for operations that must finish before proceeding -- Use `WaitUntil.Started` when you want to poll manually or run operations in parallel -- Use DefaultAzureCredential for **local development only**. In production, use ManagedIdentityCredential — see [auth-best-practices.md](../auth-best-practices.md) -- Handle `RequestFailedException` for ARM API errors -- Use `CreateOrUpdateAsync` for idempotent operations -- Navigate hierarchy via `Get*` methods (e.g., `cache.GetRedisFirewallRules()`) -- Use Premium SKU for production workloads requiring geo-replication, clustering, or persistence -- Enable TLS 1.2 minimum — set `MinimumTlsVersion = RedisTlsVersion.Tls1_2` -- Disable non-SSL port — set `EnableNonSslPort = false` for security -- Rotate keys regularly — use `RegenerateKeyAsync` and update connection strings diff --git a/plugin/skills/azure-cost/cost-optimization/services/appservice/azure-app-service.md b/plugin/skills/azure-cost/cost-optimization/services/appservice/azure-app-service.md index 1bb9b1adf..63150ca53 100644 --- a/plugin/skills/azure-cost/cost-optimization/services/appservice/azure-app-service.md +++ b/plugin/skills/azure-cost/cost-optimization/services/appservice/azure-app-service.md @@ -2,6 +2,8 @@ Reduce App Service costs through plan rightsizing, idle slot cleanup, and dev/test pricing. +> **Important:** Always present the total bill and cost breakdown (Step 2+ of the [cost optimization workflow](../../workflow.md)) alongside these recommendations — don't produce savings advice from this guide alone. + ## Subscription Input Options Accept any of these to scope the analysis: Subscription ID, Subscription Name, Resource Group, or "All my subscriptions". @@ -10,14 +12,13 @@ Accept any of these to scope the analysis: Subscription ID, Subscription Name, R | Priority | Rule | Detection Logic | Recommendation | Avg Savings | |----------|------|----------------|----------------|-------------| -| 🔴 Critical | Stopped App on Paid Plan | Site `properties.state == 'Stopped'` and associated plan tier is not Free/Shared | Delete the app or move it to a Free/Shared plan; if it’s the only app on the plan, delete/scale down the plan | $50-500/mo | +| 🔴 Critical | Stopped App on Paid Plan | Site `properties.state == 'Stopped'` joined to its plan (`serverFarmId`) where the plan's `sku.tier` is not Free/Shared/Dynamic | Delete the app or move it to a Free/Shared plan; if it’s the only app on the plan, delete/scale down the plan | $50-500/mo | | 🔴 Critical | Empty App Service Plan | Plan has zero apps deployed | Delete the plan | $50-400/mo | -| 🟠 High | Premium in Non-Production | `sku.tier in ['PremiumV2','PremiumV3']` AND `tags.environment in ['dev','test','staging']` | Downgrade to Basic or Standard | $100-600/mo | +| 🟠 High | Premium in Non-Production | Plan `sku.tier in ['Premium','PremiumV2','PremiumV3']` AND `tags.environment in ['dev','test','staging','sandbox']` | Downgrade to Basic or Standard | $100-600/mo | | 🟠 High | Idle Deployment Slots | Non-production slots with zero traffic for 14+ days | Delete unused slots (slots share plan workers but increase utilization, driving scale-out) | $30-150/mo | | 🟠 High | Over-Provisioned Plan | CPU avg <20% AND memory avg <30% over 14 days | Scale down SKU or reduce instance count | $50-400/mo | | 🟡 Medium | No Auto-Scale Rules | Production plan with fixed instance count >2 | Add auto-scale rules to scale in during low traffic | $30-200/mo | | 🟡 Medium | Missing Dev/Test Pricing | Dev/test workloads on regular pricing | Apply Azure Dev/Test subscription offer (subscription-level, not plan-level) | 30-55% savings | -| 🟡 Medium | Always On for Non-Production | `alwaysOn == true` on dev/test apps | Disable Always On (apps cold-start on first request) | Reduced idle cost | | 🟢 Low | Untagged App Service | Missing `environment`, `owner`, or `costCenter` tags | Apply tags for cost allocation | N/A | | 🟢 Low | Old Deployment Slots | Slots older than 90 days not used for blue-green | Review if still needed | Variable | @@ -25,21 +26,28 @@ Accept any of these to scope the analysis: Subscription ID, Subscription Name, R | Workload | Recommended Tier | Key Features | |----------|-----------------|--------------| -| Dev/test, prototypes | Free / Basic B1 | No SLA, limited scale | +| Dev/test, prototypes | Free F1 | No SLA, limited scale (60 CPU-min/day) | +| Light workloads needing an SLA | Basic B1 | 99.95% SLA, dedicated compute, no auto-scale/slots | | Low-traffic production | Standard S1 | Auto-scale, slots, backups | | High-traffic production | Premium P1v3 | Better perf, more slots, VNET | | Isolated compliance | Isolated I1v2 | Private environment, max scale | ## Resource Graph Queries -**Find stopped apps (review for deletion or plan downgrade):** +**Find stopped apps on paid plans (review for deletion or plan downgrade):** ```kql Resources | where type =~ 'microsoft.web/sites' | where properties.state =~ 'Stopped' -| where isnotempty(properties.serverFarmId) -| project name, resourceGroup, kind, state=properties.state +| extend planId = tolower(tostring(properties.serverFarmId)) +| join kind=inner ( + Resources + | where type =~ 'microsoft.web/serverfarms' + | project planId = tolower(id), planSku = tostring(sku.name), planTier = tostring(sku.tier) + ) on planId +| where planTier !in~ ('Free', 'Shared', 'Dynamic') +| project name, resourceGroup, planSku, planTier ``` **Find empty App Service Plans:**