← Back to Skills

bicep-modules

active

Generates Azure Bicep module files following established project conventions, with secure-by-default and cost-optimised configurations. Reads per-resource .md reference files for security baselines, cost guidelines, and code patterns. Use when creating a new Bicep module, adding a resource to an existing module, writing a role assignment module, or reviewing Bicep for convention violations. Validates parameter ordering, decorator patterns, security defaults (RBAC, soft delete, TLS 1.2, private endpoints), cost-appropriate SKU defaults, idiomatic output ordering, and code patterns (role GUIDs, conditional resources, private endpoints, parent-child relationships, null-safe access). Do NOT use for Terraform, ARM JSON, or Pulumi authoring.

Owner: mds Category: infrastructure Version: 1.0.0 Tokens: ~7k
azurebicepiacinfrastructure
100

Quality Score Breakdown

Structure (15%) 18/18
Show checks (11)
  • SKILL.md exists with exact casing 3/3
  • Valid YAML frontmatter 3/3
  • No unexpected frontmatter keys 1/1
  • Name field valid (kebab-case) 2/2
  • Name matches folder name 1/1
  • Description field present 2/2
  • No angle brackets in frontmatter 1/1
  • Folder name is kebab-case 1/1
  • No README.md inside skill folder 1/1
  • Test directory with test-cases.yml exists 2/2
  • Status 'active' is valid 1/1
Description (20%) 22/22
Show checks (7)
  • Contains action verbs: generates, validates 4/4
  • Contains trigger indicators: use when, use for 5/5
  • Description is specific and actionable 4/4
  • File types mentioned in description 3/3
  • Description length: 746/1024 chars 2/2
  • Has negative triggers (scope boundaries) 2/2
  • Owner/author specified in metadata 2/2
Instructions (25%) 28/28
Show checks (8)
  • Skill body has content 3/3
  • Has step/section structure 4/4
  • Includes examples 5/5
  • Includes error handling 4/4
  • Uses progressive disclosure (references/scripts) 4/4
  • Actionable language: 4/10 verb patterns found 3/3
  • Word count: 953/5000 2/2
  • All referenced paths exist 3/3
Test Coverage (25%) 29/29
Show checks (10)
  • test-cases.yml exists and parses 3/3
  • 6 should-trigger tests ✓ 4/4
  • 4 should-not-trigger tests ✓ 3/3
  • 7 functional tests ✓ 5/5
  • 2 negative tests ✓ 3/3
  • 2 edge case tests ✓ 3/3
  • Performance baseline documented 2/2
  • All functional tests have ≥2 assertions 2/2
  • All trigger phrases are diverse 2/2
  • All assertions are specific 2/2
Security (15%) 15/15
Show checks (5)
  • No secrets detected 5/5
  • No injection vectors in frontmatter 3/3
  • Name is not reserved 3/3
  • No suspicious code patterns 2/2
  • External URLs: 0 2/2

Test Coverage

6
Should Trigger
4
Should Not Trigger
7
Functional
2
Negative
2
Edge Cases
No
LLM Evals

Bicep Modules

Workflow

Always follow this order for every module task:

  1. Identify resource type — determine the Azure resource being authored or modified
  2. Read security baselines — open references/security-baselines.md and locate the section for this resource type; apply all defaults
  3. Read cost guidelines — if SKU or tier selection is needed, open references/cost-guidelines.md and apply the appropriate default with upgrade path documented in the parameter description
  4. Read conventions — open references/conventions.md for role assignments, private endpoints, network rule construction, App Config key-values, or any complex structural pattern
  5. Produce the module — write the complete Bicep file
  6. Self-check — verify against the checklist at the bottom of this file before returning output

File and Naming Rules

RuleConvention
File namescamelCase: keyVault.bicep, containerApp.bicep, blobStorage.bicep
Role assignment files<resource>RoleAssignment.bicep (e.g., keyVaultRoleAssignment.bicep)
All identifierscamelCase — resources, variables, parameters, outputs
API versionsPinned to a specific date (e.g., @2023-07-01); stable preferred; preview acceptable when no stable alternative exists (add a comment explaining why)
Target scopeNo targetScope declaration — all modules use implicit resource group scope

Parameter Block Rules

Ordering

name (or primary identifier)
location
[required resource-specific params]
[optional resource-specific params]
tags object = {}
  • name is first unless the primary identifier has a resource-specific name (e.g., appName, storeName, keyVaultName) — use that instead
  • location string = resourceGroup().location — always included, placed immediately after the name/identifier group
  • tags object = {} — always last, no exceptions

Decorator Order

Apply decorators in this order (omit those that don’t apply):

  1. @description('...') — always present, every parameter
  2. @metadata({ example: ... }) — for complex array/object parameters only, placed after @description()
  3. @allowed([...])
  4. @minLength() / @maxLength()
  5. @minValue() / @maxValue()
  6. @secure() — last decorator, applies only to string parameters (never object)

Examples

@description('Name of the storage account (3–24 lowercase alphanumeric characters only)')
@minLength(3)
@maxLength(24)
param name string

@description('Administrator password')
@secure()
param administratorPassword string

@description('SKU tier for the search service')
@allowed([
  'basic'
  'standard'
  'standard2'
])
param sku string = 'basic'

@description('Database names to create')
@metadata({ example: ['app', 'analytics'] })
param databaseNames array = ['app']

Code Pattern Quick Reference

PatternForm
Conditional resourceresource x '...' = if (condition) { ... }
Private endpoint flagvar usePrivateEndpoint = !empty(privateEndpointSubnetId)
Extract name from IDlast(split(resourceId, '/'))!
Null-safe field accessobj.?field ?? defaultValue
Parent-child resourceparent: parentResource — never string concatenation
Existing resource referenceresource x '...' existing = { name: name }
Loop output[for (item, i) in items: resources[i].property]
Purge protection (never false)enablePurgeProtection: param ? true : null
Network rules from arrays[for subnetId in allowedSubnetIds: { id: subnetId, ignoreMissingVnetServiceEndpoint: false }]

Full pattern implementations with context: see references/conventions.md.


Output Block Rules

  • Every output has @description() — no exceptions
  • Order: idname → resource-specific outputs (endpoint, fqdn, principalId, etc.)
  • Child resources (e.g., keyVaultSecret, appConfigKeyValue): no independent .id — start with name
  • Role assignment modules: NO outputs at all
@description('The resource ID of the Key Vault')
output id string = keyVault.id

@description('The name of the Key Vault')
output name string = keyVault.name

@description('The URI of the Key Vault')
output vaultUri string = keyVault.properties.vaultUri

Common Issues and Troubleshooting

IssueResolution
enablePurgeProtection: false rejected by ARMUse param ? true : null — ARM rejects false after the feature is enabled; omit the property to leave it unchanged
Conditional resource compile errorBoth the conditional resource and any parent: child resources must share the same if (condition) guard
last(split(...)) type errorAppend ! non-null assertion: last(split(resourceId, '/'))!
Role assignment fails with duplicate nameThe guid() call must include roleName as a third argument — omitting it causes collisions when assigning multiple roles to the same principal
Private DNS zone group depends-on errorDNS zone group must use parent: privateEndpoint (not name: concat) and be guarded by the same if (usePrivateEndpoint) condition
Preview API version with no stable alternativeAdd an inline comment: // No stable API version available as of <date>

Self-Check Checklist

Before returning any module, verify:

  • @description() on every parameter and every output
  • name / primary identifier is first parameter; tags is last
  • Decorator order: @description@metadata@allowed@minLength/@maxLength@minValue/@maxValue@secure
  • Security defaults applied per references/security-baselines.md for this resource type
  • SKU/tier defaults are cost-appropriate per references/cost-guidelines.md; upgrade path documented in parameter description
  • API version is pinned to a specific date; stable preferred; preview has explanatory comment
  • No targetScope declaration
  • First output is id, second is name — unless child resource (start with name) or role assignment (no outputs)
  • enablePurgeProtection uses param ? true : null pattern — never false
  • Conditional resources use = if (condition) pattern
  • Private endpoint uses var usePrivateEndpoint = !empty(...) flag
  • Parent-child resources use parent: — never name string concatenation
  • @secure() only on string parameters, never object
  • Role assignment modules: principalType: 'ServicePrincipal' hardcoded; no outputs
  • App Config key-values: key format App:Component:Setting (PascalCase); secrets via KV reference