@slamb2k/mad-skills 2.0.28 → 2.0.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mad-skills",
3
3
  "description": "AI-assisted planning, development and governance tools",
4
- "version": "2.0.28",
4
+ "version": "2.0.29",
5
5
  "author": {
6
6
  "name": "slamb2k",
7
7
  "url": "https://github.com/slamb2k"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slamb2k/mad-skills",
3
- "version": "2.0.28",
3
+ "version": "2.0.29",
4
4
  "description": "Claude Code skills collection — full lifecycle development tools",
5
5
  "type": "module",
6
6
  "repository": {
@@ -81,6 +81,27 @@ Status icons: ✅ done · ❌ failed · ⚠️ degraded · ⏳ working · ⏭️
81
81
 
82
82
  ---
83
83
 
84
+ ## Platform Detection
85
+
86
+ Detect the hosting platform **before** pre-flight so dependency checks are
87
+ platform-specific:
88
+
89
+ ```bash
90
+ REMOTE_URL=$(git remote get-url origin 2>/dev/null)
91
+ if echo "$REMOTE_URL" | grep -qiE 'dev\.azure\.com|visualstudio\.com'; then
92
+ PLATFORM="azdo"
93
+ elif echo "$REMOTE_URL" | grep -qi 'github\.com'; then
94
+ PLATFORM="github"
95
+ else
96
+ PLATFORM="github" # default fallback
97
+ fi
98
+ ```
99
+
100
+ Pass `{PLATFORM}` into all phase prompts. Each phase uses the appropriate
101
+ CLI tool and registry defaults based on the detected platform.
102
+
103
+ ---
104
+
84
105
  ## Pre-flight
85
106
 
86
107
  Before starting, check dependencies:
@@ -90,12 +111,101 @@ Before starting, check dependencies:
90
111
  | docker | cli | `docker --version` | no | ask | Needed for local build verification; skip verify phase if absent |
91
112
  | git | cli | `git --version` | yes | stop | Install from https://git-scm.com |
92
113
  | sync | skill | `~/.claude/skills/sync/SKILL.md` or `~/.claude/plugins/marketplaces/slamb2k/skills/sync/SKILL.md` | no | fallback | Repo sync; falls back to manual git pull |
114
+ | az devops | cli | `az devops -h 2>/dev/null` | no | fallback | Falls back to REST API with PAT; see AzDO tooling below |
115
+
116
+ **Platform-conditional rules:**
117
+ - **`az devops`**: Only checked when `PLATFORM == azdo`. Skip for GitHub repos.
118
+
119
+ For each applicable row, in order:
120
+ 1. Skip rows that don't apply to the detected `{PLATFORM}`
121
+ 2. Run the Check command (for cli/npm) or test file existence (for agent/skill)
122
+ 3. If found: continue silently
123
+ 4. If missing: apply Resolution strategy
124
+ - **stop**: notify user with Detail, halt execution
125
+ - **url**: notify user with Detail (install link), halt execution
126
+ - **install**: notify user, run the command in Detail, continue if successful
127
+ - **ask**: notify user, offer to run command in Detail, continue either way (or halt if required)
128
+ - **fallback**: notify user with Detail, continue with degraded behavior
129
+ 5. After all checks: summarize what's available and what's degraded
130
+
131
+ ### AzDO Tooling Detection
132
+
133
+ When `PLATFORM == azdo`, determine which tooling is available. Set `AZDO_MODE`
134
+ for use in all subsequent phases:
135
+
136
+ ```bash
137
+ if az devops -h &>/dev/null; then
138
+ AZDO_MODE="cli"
139
+ else
140
+ AZDO_MODE="rest"
141
+ fi
142
+ ```
143
+
144
+ - **`cli`**: Use `az repos` / `az pipelines` commands (preferred)
145
+ - **`rest`**: Use Azure DevOps REST API via `curl`. Requires a PAT (personal
146
+ access token) in `AZURE_DEVOPS_EXT_PAT` or `AZDO_PAT` env var. If no PAT
147
+ is found, prompt the user to either install the CLI or set the env var.
148
+
149
+ Report in pre-flight:
150
+ - ✅ `az devops cli` — version found
151
+ - ⚠️ `az devops cli` — not found → using REST API fallback
152
+ - ❌ `az devops cli` — not found, no PAT configured → halt with setup instructions
153
+
154
+ ### AzDO Configuration Validation
155
+
156
+ When `PLATFORM == azdo`, extract organization and project from the remote URL
157
+ and validate they are usable. These values are needed by every `az repos` /
158
+ `az pipelines` command and every REST API call.
159
+
160
+ ```bash
161
+ # Extract org and project from remote URL patterns:
162
+ # https://dev.azure.com/{ORG}/{PROJECT}/_git/{REPO}
163
+ # https://{ORG}@dev.azure.com/{ORG}/{PROJECT}/_git/{REPO}
164
+ # {ORG}@vs-ssh.visualstudio.com:v3/{ORG}/{PROJECT}/{REPO}
165
+
166
+ REMOTE_URL=$(git remote get-url origin 2>/dev/null)
167
+
168
+ if echo "$REMOTE_URL" | grep -q 'dev\.azure\.com'; then
169
+ AZDO_ORG=$(echo "$REMOTE_URL" | sed -n 's|.*dev\.azure\.com/\([^/]*\)/.*|\1|p')
170
+ AZDO_PROJECT=$(echo "$REMOTE_URL" | sed -n 's|.*dev\.azure\.com/[^/]*/\([^/]*\)/.*|\1|p')
171
+ AZDO_ORG_URL="https://dev.azure.com/$AZDO_ORG"
172
+ elif echo "$REMOTE_URL" | grep -q 'vs-ssh\.visualstudio\.com'; then
173
+ AZDO_ORG=$(echo "$REMOTE_URL" | sed -n 's|.*vs-ssh\.visualstudio\.com:v3/\([^/]*\)/.*|\1|p')
174
+ AZDO_PROJECT=$(echo "$REMOTE_URL" | sed -n 's|.*vs-ssh\.visualstudio\.com:v3/[^/]*/\([^/]*\)/.*|\1|p')
175
+ AZDO_ORG_URL="https://dev.azure.com/$AZDO_ORG"
176
+ elif echo "$REMOTE_URL" | grep -q 'visualstudio\.com'; then
177
+ AZDO_ORG=$(echo "$REMOTE_URL" | sed -n 's|.*//\([^.]*\)\.visualstudio\.com.*|\1|p')
178
+ AZDO_PROJECT=$(echo "$REMOTE_URL" | sed -n 's|.*/\([^/]*\)/_git/.*|\1|p')
179
+ AZDO_ORG_URL="https://dev.azure.com/$AZDO_ORG"
180
+ fi
181
+
182
+ if [ -z "$AZDO_ORG" ] || [ -z "$AZDO_PROJECT" ]; then
183
+ echo "❌ Could not extract organization/project from remote URL"
184
+ echo " Remote: $REMOTE_URL"
185
+ echo ""
186
+ echo "Ensure the remote URL follows one of these formats:"
187
+ echo " https://dev.azure.com/{ORG}/{PROJECT}/_git/{REPO}"
188
+ echo " https://{ORG}.visualstudio.com/{PROJECT}/_git/{REPO}"
189
+ echo " {ORG}@vs-ssh.visualstudio.com:v3/{ORG}/{PROJECT}/{REPO}"
190
+ # HALT — cannot proceed without org/project context
191
+ fi
192
+ ```
193
+
194
+ When `AZDO_MODE == cli`, also configure the defaults so commands work correctly:
195
+ ```bash
196
+ az devops configure --defaults organization="$AZDO_ORG_URL" project="$AZDO_PROJECT"
197
+ ```
198
+
199
+ When `AZDO_MODE == rest`, store these for API calls:
200
+ - Base URL: `$AZDO_ORG_URL/$AZDO_PROJECT/_apis`
201
+ - Auth header: `Authorization: Basic $(echo -n ":$PAT" | base64)`
202
+
203
+ Report in pre-flight:
204
+ - ✅ `azdo context` — org: `{AZDO_ORG}`, project: `{AZDO_PROJECT}`
205
+ - ❌ `azdo context` — could not parse from remote URL → halt with instructions
93
206
 
94
- For each row:
95
- 1. Run the Check command
96
- 2. If found: continue silently
97
- 3. If missing: apply Resolution strategy
98
- 4. After all checks: summarize availability
207
+ Pass `{AZDO_MODE}`, `{AZDO_ORG}`, `{AZDO_PROJECT}`, `{AZDO_ORG_URL}` into
208
+ all phase prompts alongside `{PLATFORM}`.
99
209
 
100
210
  ---
101
211
 
@@ -206,8 +316,10 @@ Optional — ask only if deploying to platforms that need this:
206
316
  - **Blue-green**: maintain two environments, swap traffic
207
317
  - **Canary**: gradual traffic shift with automatic rollback on error thresholds
208
318
 
209
- **If `--skip-interview` flag**: Use detected defaults + sensible defaults for
210
- everything else (ghcr.io, 3-stage, GitHub Secrets, simple rollback).
319
+ **If `--skip-interview` flag**: Use detected defaults + sensible platform-aware
320
+ defaults for everything else:
321
+ - **GitHub** (`PLATFORM == github`): ghcr.io, 3-stage, GitHub Secrets, simple rollback
322
+ - **Azure DevOps** (`PLATFORM == azdo`): Azure Container Registry, 3-stage, Azure Key Vault, simple rollback
211
323
 
212
324
  Compile all answers into a DOCK_CONFIG object for Phase 3.
213
325
 
@@ -141,6 +141,9 @@ CMD ["gunicorn", "--bind", "0.0.0.0:{PORT}", "--workers", "4", "app:app"]
141
141
  FROM python:3.12-slim AS deps
142
142
  WORKDIR /app
143
143
  COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
144
+ # NOTE: ghcr.io/astral-sh/uv is the official uv installer image, not your app registry.
145
+ # If ghcr.io is blocked (e.g., corporate firewall), mirror this image to your
146
+ # internal registry: COPY --from={INTERNAL_REGISTRY}/astral-sh/uv:latest /uv /usr/local/bin/uv
144
147
  COPY pyproject.toml uv.lock ./
145
148
  RUN uv sync --frozen --no-dev --no-install-project
146
149
 
@@ -117,7 +117,9 @@ Mapping:
117
117
  - GitHub Actions → suggest ghcr.io
118
118
  - Azure Pipelines → suggest Azure Container Registry
119
119
  - GitLab CI → suggest GitLab Container Registry
120
- - No CI → suggest ghcr.io (most universal)
120
+ - No CI → detect platform from git remote URL:
121
+ - AzDO remote → suggest Azure Container Registry
122
+ - GitHub remote or unknown → suggest ghcr.io (most universal)
121
123
 
122
124
  ### Environment Topology
123
125
 
@@ -181,6 +183,10 @@ Options:
181
183
  5. Doppler
182
184
  6. 1Password (via CLI)
183
185
  7. HashiCorp Vault
186
+
187
+ Default selection based on platform:
188
+ - `PLATFORM == github` → default to GitHub Secrets / Variables
189
+ - `PLATFORM == azdo` → default to Azure Key Vault
184
190
  ```
185
191
 
186
192
  ### Rollback Strategy
@@ -32,5 +32,14 @@
32
32
  { "type": "semantic", "value": "Supports different deployment targets per environment" },
33
33
  { "type": "semantic", "value": "References both Azure Container Apps and Dokku as deployment platforms" }
34
34
  ]
35
+ },
36
+ {
37
+ "name": "azdo-awareness",
38
+ "prompt": "/dock for a Python FastAPI app in an Azure DevOps repo with azure-pipelines.yml already present",
39
+ "assertions": [
40
+ { "type": "semantic", "value": "Detects or acknowledges Azure DevOps as the platform based on the repository context" },
41
+ { "type": "semantic", "value": "Suggests Azure Container Registry rather than ghcr.io as the default registry" },
42
+ { "type": "semantic", "value": "References Azure Pipelines as the CI system for the generated workflow" }
43
+ ]
35
44
  }
36
45
  ]
@@ -68,9 +68,31 @@ Stage headers: `━━ {N} · {Name} ━━━━━━━━━━━━━`. I
68
68
 
69
69
  ---
70
70
 
71
+ ## Platform Detection
72
+
73
+ Detect the hosting platform **before** pre-flight so dependency checks are
74
+ platform-specific:
75
+
76
+ ```bash
77
+ REMOTE_URL=$(git remote get-url origin 2>/dev/null)
78
+ if echo "$REMOTE_URL" | grep -qiE 'dev\.azure\.com|visualstudio\.com'; then
79
+ PLATFORM="azdo"
80
+ elif echo "$REMOTE_URL" | grep -qi 'github\.com'; then
81
+ PLATFORM="github"
82
+ else
83
+ PLATFORM="github" # default fallback
84
+ fi
85
+ ```
86
+
87
+ Pass `{PLATFORM}` into all phase prompts. Each phase uses the appropriate
88
+ CLI tool: `gh` for GitHub, `az repos`/`az pipelines` for Azure DevOps.
89
+
90
+ ---
91
+
71
92
  ## Pre-flight
72
93
 
73
- Before starting, check dependencies:
94
+ Before starting, check all dependencies in this table. The table contains
95
+ **all** dependencies — some are platform-conditional (see notes after table).
74
96
 
75
97
  | Dependency | Type | Check | Required | Resolution | Detail |
76
98
  |-----------|------|-------|----------|------------|--------|
@@ -80,10 +102,107 @@ Before starting, check dependencies:
80
102
  | az | cli | `az --version` | no | fallback | Needed for Bicep; suggest install from https://aka.ms/install-azure-cli |
81
103
  | pulumi | cli | `pulumi version` | no | fallback | Detected if user wants Pulumi; suggest install from https://pulumi.com |
82
104
  | cdk | cli | `cdk --version` | no | fallback | Detected if user wants AWS CDK; install via npm |
105
+ | gh | cli | `gh --version` | yes | url | https://cli.github.com |
106
+ | az devops | cli | `az devops -h 2>/dev/null` | no | fallback | Falls back to REST API with PAT; see AzDO tooling below |
107
+
108
+ **Platform-conditional rules:**
109
+ - **`gh`**: Only required when `PLATFORM == github`. Skip for AzDO repos.
110
+ - **`az devops`**: Only checked when `PLATFORM == azdo`. Skip for GitHub repos.
83
111
 
84
- Only check the tool that matches the user's choice (or detected default).
112
+ Only check the IaC tool row that matches the user's choice (or detected default).
85
113
  Skip checks for tools not being used.
86
114
 
115
+ For each applicable row, in order:
116
+ 1. Skip rows that don't apply to the detected `{PLATFORM}`
117
+ 2. Run the Check command (for cli/npm) or test file existence (for agent/skill)
118
+ 3. If found: continue silently
119
+ 4. If missing: apply Resolution strategy
120
+ - **stop**: notify user with Detail, halt execution
121
+ - **url**: notify user with Detail (install link), halt execution
122
+ - **install**: notify user, run the command in Detail, continue if successful
123
+ - **ask**: notify user, offer to run command in Detail, continue either way (or halt if required)
124
+ - **fallback**: notify user with Detail, continue with degraded behavior
125
+ 5. After all checks: summarize what's available and what's degraded
126
+
127
+ ### AzDO Tooling Detection
128
+
129
+ When `PLATFORM == azdo`, determine which tooling is available. Set `AZDO_MODE`
130
+ for use in all subsequent phases:
131
+
132
+ ```bash
133
+ if az devops -h &>/dev/null; then
134
+ AZDO_MODE="cli"
135
+ else
136
+ AZDO_MODE="rest"
137
+ fi
138
+ ```
139
+
140
+ - **`cli`**: Use `az repos` / `az pipelines` commands (preferred)
141
+ - **`rest`**: Use Azure DevOps REST API via `curl`. Requires a PAT (personal
142
+ access token) in `AZURE_DEVOPS_EXT_PAT` or `AZDO_PAT` env var. If no PAT
143
+ is found, prompt the user to either install the CLI or set the env var.
144
+
145
+ Report in pre-flight:
146
+ - ✅ `az devops cli` — version found
147
+ - ⚠️ `az devops cli` — not found → using REST API fallback
148
+ - ❌ `az devops cli` — not found, no PAT configured → halt with setup instructions
149
+
150
+ ### AzDO Configuration Validation
151
+
152
+ When `PLATFORM == azdo`, extract organization and project from the remote URL
153
+ and validate they are usable. These values are needed by every `az repos` /
154
+ `az pipelines` command and every REST API call.
155
+
156
+ ```bash
157
+ # Extract org and project from remote URL patterns:
158
+ # https://dev.azure.com/{ORG}/{PROJECT}/_git/{REPO}
159
+ # https://{ORG}@dev.azure.com/{ORG}/{PROJECT}/_git/{REPO}
160
+ # {ORG}@vs-ssh.visualstudio.com:v3/{ORG}/{PROJECT}/{REPO}
161
+
162
+ REMOTE_URL=$(git remote get-url origin 2>/dev/null)
163
+
164
+ if echo "$REMOTE_URL" | grep -q 'dev\.azure\.com'; then
165
+ AZDO_ORG=$(echo "$REMOTE_URL" | sed -n 's|.*dev\.azure\.com/\([^/]*\)/.*|\1|p')
166
+ AZDO_PROJECT=$(echo "$REMOTE_URL" | sed -n 's|.*dev\.azure\.com/[^/]*/\([^/]*\)/.*|\1|p')
167
+ AZDO_ORG_URL="https://dev.azure.com/$AZDO_ORG"
168
+ elif echo "$REMOTE_URL" | grep -q 'vs-ssh\.visualstudio\.com'; then
169
+ AZDO_ORG=$(echo "$REMOTE_URL" | sed -n 's|.*vs-ssh\.visualstudio\.com:v3/\([^/]*\)/.*|\1|p')
170
+ AZDO_PROJECT=$(echo "$REMOTE_URL" | sed -n 's|.*vs-ssh\.visualstudio\.com:v3/[^/]*/\([^/]*\)/.*|\1|p')
171
+ AZDO_ORG_URL="https://dev.azure.com/$AZDO_ORG"
172
+ elif echo "$REMOTE_URL" | grep -q 'visualstudio\.com'; then
173
+ AZDO_ORG=$(echo "$REMOTE_URL" | sed -n 's|.*//\([^.]*\)\.visualstudio\.com.*|\1|p')
174
+ AZDO_PROJECT=$(echo "$REMOTE_URL" | sed -n 's|.*/\([^/]*\)/_git/.*|\1|p')
175
+ AZDO_ORG_URL="https://dev.azure.com/$AZDO_ORG"
176
+ fi
177
+
178
+ if [ -z "$AZDO_ORG" ] || [ -z "$AZDO_PROJECT" ]; then
179
+ echo "❌ Could not extract organization/project from remote URL"
180
+ echo " Remote: $REMOTE_URL"
181
+ echo ""
182
+ echo "Ensure the remote URL follows one of these formats:"
183
+ echo " https://dev.azure.com/{ORG}/{PROJECT}/_git/{REPO}"
184
+ echo " https://{ORG}.visualstudio.com/{PROJECT}/_git/{REPO}"
185
+ echo " {ORG}@vs-ssh.visualstudio.com:v3/{ORG}/{PROJECT}/{REPO}"
186
+ # HALT — cannot proceed without org/project context
187
+ fi
188
+ ```
189
+
190
+ When `AZDO_MODE == cli`, also configure the defaults so commands work correctly:
191
+ ```bash
192
+ az devops configure --defaults organization="$AZDO_ORG_URL" project="$AZDO_PROJECT"
193
+ ```
194
+
195
+ When `AZDO_MODE == rest`, store these for API calls:
196
+ - Base URL: `$AZDO_ORG_URL/$AZDO_PROJECT/_apis`
197
+ - Auth header: `Authorization: Basic $(echo -n ":$PAT" | base64)`
198
+
199
+ Report in pre-flight:
200
+ - ✅ `azdo context` — org: `{AZDO_ORG}`, project: `{AZDO_PROJECT}`
201
+ - ❌ `azdo context` — could not parse from remote URL → halt with instructions
202
+
203
+ Pass `{AZDO_MODE}`, `{AZDO_ORG}`, `{AZDO_PROJECT}`, `{AZDO_ORG_URL}` into
204
+ all phase prompts alongside `{PLATFORM}`.
205
+
87
206
  ---
88
207
 
89
208
  ## Phase 0: Sync
@@ -360,6 +479,13 @@ Use the provider-specific template from `references/iac-pipeline-templates.md#bo
360
479
 
361
480
  Read the appropriate template from `references/iac-pipeline-templates.md`.
362
481
 
482
+ **Platform branching:**
483
+ - When `PLATFORM == github`: Generate a GitHub Actions workflow. Output file:
484
+ `.github/workflows/infra.yml`. Use templates from the "GitHub Actions" section.
485
+ - When `PLATFORM == azdo`: Generate an Azure DevOps Pipelines YAML file. Output
486
+ file: `azure-pipelines-infra.yml`. Use templates from the "Azure DevOps
487
+ Pipelines" section.
488
+
363
489
  Generate a workflow that implements:
364
490
 
365
491
  **On pull request:**
@@ -408,8 +534,16 @@ for pushing images, the compute endpoint for deploying containers, etc.
408
534
  ### 3.5 — Environment Variables Bridge
409
535
 
410
536
  Generate a script or workflow step that reads IaC outputs and writes them as
411
- CI/CD secrets/variables for /dock's pipelines:
537
+ CI/CD secrets/variables for /dock's pipelines.
412
538
 
539
+ **Platform branching:**
540
+ - When `PLATFORM == github`: Use `gh secret set` / `gh variable set` commands.
541
+ Reference `references/iac-pipeline-templates.md#infra/sync-outputs.sh`.
542
+ - When `PLATFORM == azdo`: Use `az pipelines variable-group variable update` /
543
+ `az pipelines variable-group variable create` commands. Reference
544
+ `references/iac-pipeline-templates.md#infra/sync-outputs.sh (Azure DevOps)`.
545
+
546
+ **GitHub example:**
413
547
  ```bash
414
548
  # infra/sync-outputs.sh
415
549
  REGISTRY_URL=$(terraform output -raw registry_url)
@@ -417,6 +551,14 @@ gh secret set REGISTRY_URL --body "$REGISTRY_URL"
417
551
  gh secret set DATABASE_URL --body "$(terraform output -raw database_connection_string)"
418
552
  ```
419
553
 
554
+ **Azure DevOps example:**
555
+ ```bash
556
+ # infra/sync-outputs.sh
557
+ REGISTRY_URL=$(terraform output -raw registry_url)
558
+ az pipelines variable-group variable update --group-id "$VG_ID" --name REGISTRY_URL --value "$REGISTRY_URL"
559
+ az pipelines variable-group variable update --group-id "$VG_ID" --name DATABASE_URL --value "$(terraform output -raw database_connection_string)" --secret true
560
+ ```
561
+
420
562
  ---
421
563
 
422
564
  ## Phase 4: Verify
@@ -10,6 +10,8 @@ Template variables:
10
10
  - `{STATE_BACKEND}`: backend config details
11
11
  - `{INFRA_DIR}`: path to IaC files (default: infra/)
12
12
  - `{ENVIRONMENTS}`: list of environments (dev, staging, prod)
13
+ - `{PLATFORM}`: github or azdo — determines which pipeline template to use
14
+ - `{SERVICE_CONNECTION}`: Azure DevOps service connection name (AzDO only)
13
15
 
14
16
  ---
15
17
 
@@ -411,6 +413,96 @@ stages:
411
413
  environmentServiceNameAzureRM: $(serviceConnection)
412
414
  ```
413
415
 
416
+ ### Bicep
417
+
418
+ File: `azure-pipelines-infra.yml`
419
+
420
+ ```yaml
421
+ trigger:
422
+ branches:
423
+ include: [{DEFAULT_BRANCH}]
424
+ paths:
425
+ include: [infra/*]
426
+
427
+ pr:
428
+ paths:
429
+ include: [infra/*]
430
+
431
+ variables:
432
+ infraDir: infra
433
+ serviceConnection: "{SERVICE_CONNECTION}"
434
+ location: "{REGION}"
435
+
436
+ stages:
437
+ - stage: WhatIf
438
+ condition: eq(variables['Build.Reason'], 'PullRequest')
439
+ jobs:
440
+ - job: BicepWhatIf
441
+ pool:
442
+ vmImage: ubuntu-latest
443
+ steps:
444
+ - task: AzureCLI@2
445
+ displayName: Bicep What-If
446
+ inputs:
447
+ azureSubscription: $(serviceConnection)
448
+ scriptType: bash
449
+ scriptLocation: inlineScript
450
+ inlineScript: |
451
+ az deployment sub what-if \
452
+ --location $(location) \
453
+ --template-file $(infraDir)/main.bicep \
454
+ --parameters $(infraDir)/environments/dev.bicepparam \
455
+ --no-pretty-print
456
+
457
+ - stage: DeployDev
458
+ condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/{DEFAULT_BRANCH}'))
459
+ jobs:
460
+ - deployment: DeployDev
461
+ environment: dev
462
+ pool:
463
+ vmImage: ubuntu-latest
464
+ strategy:
465
+ runOnce:
466
+ deploy:
467
+ steps:
468
+ - checkout: self
469
+ - task: AzureCLI@2
470
+ displayName: Deploy Dev
471
+ inputs:
472
+ azureSubscription: $(serviceConnection)
473
+ scriptType: bash
474
+ scriptLocation: inlineScript
475
+ inlineScript: |
476
+ az deployment sub create \
477
+ --location $(location) \
478
+ --template-file $(infraDir)/main.bicep \
479
+ --parameters $(infraDir)/environments/dev.bicepparam
480
+
481
+ - stage: DeployEnv
482
+ condition: eq(variables['Build.Reason'], 'Manual')
483
+ jobs:
484
+ - deployment: DeployEnv
485
+ environment: $(targetEnvironment)
486
+ pool:
487
+ vmImage: ubuntu-latest
488
+ strategy:
489
+ runOnce:
490
+ deploy:
491
+ steps:
492
+ - checkout: self
493
+ - task: AzureCLI@2
494
+ displayName: Deploy $(targetEnvironment)
495
+ inputs:
496
+ azureSubscription: $(serviceConnection)
497
+ scriptType: bash
498
+ scriptLocation: inlineScript
499
+ inlineScript: |
500
+ az deployment sub create \
501
+ --location $(location) \
502
+ --template-file $(infraDir)/main.bicep \
503
+ --parameters $(infraDir)/environments/$(targetEnvironment).bicepparam
504
+ ```
505
+
414
506
  ---
415
507
 
416
508
  ## Bootstrap Scripts
@@ -517,3 +609,58 @@ DB_URL=$(terraform output -raw database_connection_string 2>/dev/null) || true
517
609
 
518
610
  echo "Outputs synced to GitHub."
519
611
  ```
612
+
613
+ ### infra/sync-outputs.sh (Azure DevOps)
614
+
615
+ Reads IaC outputs and sets them as Azure DevOps variable group variables for /dock pipelines:
616
+
617
+ ```bash
618
+ #!/bin/bash
619
+ # Sync Terraform outputs to Azure DevOps variable group for /dock
620
+ set -euo pipefail
621
+
622
+ cd "$(dirname "$0")"
623
+
624
+ PROJECT="{PROJECT}"
625
+ VG_NAME="${PROJECT}-infra-outputs"
626
+
627
+ echo "Syncing infrastructure outputs to Azure DevOps..."
628
+
629
+ # Resolve or create the variable group
630
+ VG_ID=$(az pipelines variable-group list \
631
+ --query "[?name=='${VG_NAME}'].id | [0]" -o tsv 2>/dev/null)
632
+
633
+ if [ -z "$VG_ID" ]; then
634
+ echo "Creating variable group: ${VG_NAME}"
635
+ VG_ID=$(az pipelines variable-group create \
636
+ --name "$VG_NAME" \
637
+ --variables placeholder=true \
638
+ --query "id" -o tsv)
639
+ fi
640
+
641
+ # Helper: update or create a variable in the group
642
+ upsert_var() {
643
+ local name="$1" value="$2" secret="${3:-false}"
644
+ if az pipelines variable-group variable list --group-id "$VG_ID" \
645
+ --query "\"${name}\"" -o tsv &>/dev/null; then
646
+ az pipelines variable-group variable update \
647
+ --group-id "$VG_ID" --name "$name" --value "$value" --secret "$secret" --output none
648
+ else
649
+ az pipelines variable-group variable create \
650
+ --group-id "$VG_ID" --name "$name" --value "$value" --secret "$secret" --output none
651
+ fi
652
+ }
653
+
654
+ # Public values
655
+ REGISTRY_URL=$(terraform output -raw registry_url 2>/dev/null) || true
656
+ [ -n "$REGISTRY_URL" ] && upsert_var "REGISTRY_URL" "$REGISTRY_URL"
657
+
658
+ COMPUTE_ENDPOINT=$(terraform output -raw compute_endpoint 2>/dev/null) || true
659
+ [ -n "$COMPUTE_ENDPOINT" ] && upsert_var "COMPUTE_ENDPOINT" "$COMPUTE_ENDPOINT"
660
+
661
+ # Sensitive values
662
+ DB_URL=$(terraform output -raw database_connection_string 2>/dev/null) || true
663
+ [ -n "$DB_URL" ] && upsert_var "DATABASE_URL" "$DB_URL" true
664
+
665
+ echo "Outputs synced to Azure DevOps variable group: ${VG_NAME} (ID: ${VG_ID})"
666
+ ```
@@ -31,5 +31,14 @@
31
31
  { "type": "semantic", "value": "Describes or generates a CI/CD pipeline that plans on pull requests and applies on merge" },
32
32
  { "type": "semantic", "value": "Mentions state management for Terraform such as S3 backend or remote state" }
33
33
  ]
34
+ },
35
+ {
36
+ "name": "azdo-awareness",
37
+ "prompt": "/keel for a Bicep project hosted in Azure DevOps",
38
+ "assertions": [
39
+ { "type": "semantic", "value": "Detects or acknowledges Azure DevOps as the hosting platform rather than assuming GitHub" },
40
+ { "type": "semantic", "value": "Mentions Azure Pipelines or Azure DevOps pipeline generation rather than GitHub Actions" },
41
+ { "type": "regex", "value": "(?i)(azure devops|azdo|azure.pipelines|az pipelines)" }
42
+ ]
34
43
  }
35
44
  ]
@@ -1,5 +1,5 @@
1
1
  {
2
- "generated": "2026-03-12T13:35:25.365Z",
2
+ "generated": "2026-03-12T15:57:08.604Z",
3
3
  "count": 10,
4
4
  "skills": [
5
5
  {
@@ -36,7 +36,7 @@
36
36
  "name": "dock",
37
37
  "directory": "dock",
38
38
  "description": ">- Generate container-based release pipelines that build once and promote immutable artifacts through environments (dev → staging → prod). Detects your stack, interviews for infrastructure choices, then outputs deterministic CI/CD files (Dockerfile, workflows, deployment manifests) that run without an LLM. Use when setting up deployment pipelines, containerizing an app, creating release workflows, or connecting CI to container-friendly infrastructure (Azure Container Apps, AWS Fargate, Google Cloud Run, Kubernetes, Dokku, Coolify, CapRover, etc.).",
39
- "lines": 394,
39
+ "lines": 506,
40
40
  "hasScripts": false,
41
41
  "hasReferences": true,
42
42
  "hasAssets": false,
@@ -46,7 +46,7 @@
46
46
  "name": "keel",
47
47
  "directory": "keel",
48
48
  "description": ">- Generate Infrastructure as Code (IaC) pipelines that provision the cloud and container infrastructure your app deploys to. Interview-driven: detects your stack and cloud provider, then outputs deterministic IaC files (Terraform, Bicep, Pulumi, or CDK) plus CI/CD pipelines that plan on PR and apply on merge. Use when setting up cloud infrastructure, provisioning container registries, databases, networking, DNS, or any infrastructure that containers deploy onto. Designed to run before /dock — /keel lays the infrastructure, /dock deploys to it.",
49
- "lines": 496,
49
+ "lines": 638,
50
50
  "hasScripts": false,
51
51
  "hasReferences": true,
52
52
  "hasAssets": false,