@slamb2k/mad-skills 2.0.41 → 2.0.42

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.41",
4
+ "version": "2.0.42",
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.41",
3
+ "version": "2.0.42",
4
4
  "description": "Claude Code skills collection — full lifecycle development tools",
5
5
  "type": "module",
6
6
  "repository": {
@@ -342,12 +342,12 @@ elif echo "$REMOTE_URL" | grep -q 'vs-ssh\.visualstudio\.com'; then
342
342
  elif echo "$REMOTE_URL" | grep -q 'visualstudio\.com'; then
343
343
  AZDO_ORG=$(echo "$REMOTE_URL" | sed -n 's|.*//\([^.]*\)\.visualstudio\.com.*|\1|p')
344
344
  AZDO_PROJECT=$(echo "$REMOTE_URL" | sed -n 's|.*/\([^/]*\)/_git/.*|\1|p')
345
- AZDO_ORG_URL="https://dev.azure.com/$AZDO_ORG"
345
+ AZDO_ORG_URL="https://${AZDO_ORG}.visualstudio.com"
346
346
  fi
347
347
  # URL-decode for CLI/display; keep URL-safe versions for REST API paths
348
348
  AZDO_PROJECT_URL_SAFE="$AZDO_PROJECT"
349
- AZDO_ORG=$(printf '%b' "${AZDO_ORG//%/\\x}")
350
- AZDO_PROJECT=$(printf '%b' "${AZDO_PROJECT//%/\\x}")
349
+ AZDO_ORG=$(python3 -c "import urllib.parse; print(urllib.parse.unquote('$AZDO_ORG'))")
350
+ AZDO_PROJECT=$(python3 -c "import urllib.parse; print(urllib.parse.unquote('$AZDO_PROJECT_URL_SAFE'))")
351
351
  REPO_NAME=$(basename -s .git "$REMOTE_URL")
352
352
  ```
353
353
 
@@ -366,7 +366,7 @@ If org/project extraction fails, report ⚠️ and skip branch policies.
366
366
 
367
367
  **REST fallback:**
368
368
  ```bash
369
- AUTH="Authorization: Basic $(echo -n ":$PAT" | base64)"
369
+ AUTH="Authorization: Basic $(printf ":%s" "$PAT" | base64 | tr -d '\n')"
370
370
  # Get repository ID first
371
371
  REPO_ID=$(curl -s -H "$AUTH" \
372
372
  "$AZDO_ORG_URL/$AZDO_PROJECT_URL_SAFE/_apis/git/repositories/$REPO_NAME?api-version=7.0" \
@@ -128,8 +128,9 @@ For each applicable row, in order:
128
128
  - **fallback**: notify user with Detail, continue with degraded behavior
129
129
  5. After all checks: summarize what's available and what's degraded
130
130
 
131
- When `PLATFORM == azdo`, follow `references/azdo-platform.md` for tooling
132
- detection (`AZDO_MODE`) and configuration validation (`AZDO_ORG`, `AZDO_PROJECT`).
131
+ When `PLATFORM == azdo`, follow the shared AzDO platform guide
132
+ (repo root: references/azdo-platform.md) for tooling detection (`AZDO_MODE`)
133
+ and configuration validation (`AZDO_ORG`, `AZDO_PROJECT`).
133
134
  Pass these variables into all phase prompts alongside `{PLATFORM}`.
134
135
 
135
136
  ---
@@ -124,8 +124,9 @@ For each applicable row, in order:
124
124
  - **fallback**: notify user with Detail, continue with degraded behavior
125
125
  5. After all checks: summarize what's available and what's degraded
126
126
 
127
- When `PLATFORM == azdo`, follow `references/azdo-platform.md` for tooling
128
- detection (`AZDO_MODE`) and configuration validation (`AZDO_ORG`, `AZDO_PROJECT`).
127
+ When `PLATFORM == azdo`, follow the shared AzDO platform guide
128
+ (repo root: references/azdo-platform.md) for tooling detection (`AZDO_MODE`)
129
+ and configuration validation (`AZDO_ORG`, `AZDO_PROJECT`).
129
130
  Pass these variables into all phase prompts alongside `{PLATFORM}`.
130
131
 
131
132
  ---
@@ -1,5 +1,5 @@
1
1
  {
2
- "generated": "2026-03-22T07:57:16.501Z",
2
+ "generated": "2026-03-24T07:52:19.071Z",
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": 431,
39
+ "lines": 432,
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": 419,
49
+ "lines": 420,
50
50
  "hasScripts": false,
51
51
  "hasReferences": true,
52
52
  "hasAssets": false,
@@ -166,13 +166,13 @@ elif echo "$REMOTE_URL" | grep -q 'vs-ssh\.visualstudio\.com'; then
166
166
  elif echo "$REMOTE_URL" | grep -q 'visualstudio\.com'; then
167
167
  AZDO_ORG=$(echo "$REMOTE_URL" | sed -n 's|.*//\([^.]*\)\.visualstudio\.com.*|\1|p')
168
168
  AZDO_PROJECT=$(echo "$REMOTE_URL" | sed -n 's|.*/\([^/]*\)/_git/.*|\1|p')
169
- AZDO_ORG_URL="https://dev.azure.com/$AZDO_ORG"
169
+ AZDO_ORG_URL="https://${AZDO_ORG}.visualstudio.com"
170
170
  fi
171
171
 
172
172
  # URL-decode for CLI/display; keep URL-safe versions for REST API paths
173
173
  AZDO_PROJECT_URL_SAFE="$AZDO_PROJECT"
174
- AZDO_ORG=$(printf '%b' "${AZDO_ORG//%/\\x}")
175
- AZDO_PROJECT=$(printf '%b' "${AZDO_PROJECT//%/\\x}")
174
+ AZDO_ORG=$(python3 -c "import urllib.parse; print(urllib.parse.unquote('$AZDO_ORG'))")
175
+ AZDO_PROJECT=$(python3 -c "import urllib.parse; print(urllib.parse.unquote('$AZDO_PROJECT_URL_SAFE'))")
176
176
 
177
177
  if [ -z "$AZDO_ORG" ] || [ -z "$AZDO_PROJECT" ]; then
178
178
  echo "❌ Could not extract organization/project from remote URL"
@@ -193,7 +193,7 @@ az devops configure --defaults organization="$AZDO_ORG_URL" project="$AZDO_PROJE
193
193
 
194
194
  When `AZDO_MODE == rest`, store these for API calls:
195
195
  - Base URL: `$AZDO_ORG_URL/$AZDO_PROJECT_URL_SAFE/_apis`
196
- - Auth header: `Authorization: Basic $(echo -n ":$PAT" | base64)`
196
+ - Auth header: `Authorization: Basic $(printf ":%s" "$PAT" | base64 | tr -d '\n')`
197
197
 
198
198
  Report in pre-flight:
199
199
  - ✅ `azdo context` — org: `{AZDO_ORG}`, project: `{AZDO_PROJECT}`
@@ -183,13 +183,13 @@ elif echo "$REMOTE_URL" | grep -q 'visualstudio\.com'; then
183
183
  # Legacy HTTPS format: https://{ORG}.visualstudio.com/{PROJECT}/_git/{REPO}
184
184
  AZDO_ORG=$(echo "$REMOTE_URL" | sed -n 's|.*//\([^.]*\)\.visualstudio\.com.*|\1|p')
185
185
  AZDO_PROJECT=$(echo "$REMOTE_URL" | sed -n 's|.*/\([^/]*\)/_git/.*|\1|p')
186
- AZDO_ORG_URL="https://dev.azure.com/$AZDO_ORG"
186
+ AZDO_ORG_URL="https://${AZDO_ORG}.visualstudio.com"
187
187
  fi
188
188
 
189
189
  # URL-decode for CLI/display; keep URL-safe versions for REST API paths
190
190
  AZDO_PROJECT_URL_SAFE="$AZDO_PROJECT"
191
- AZDO_ORG=$(printf '%b' "${AZDO_ORG//%/\\x}")
192
- AZDO_PROJECT=$(printf '%b' "${AZDO_PROJECT//%/\\x}")
191
+ AZDO_ORG=$(python3 -c "import urllib.parse; print(urllib.parse.unquote('$AZDO_ORG'))")
192
+ AZDO_PROJECT=$(python3 -c "import urllib.parse; print(urllib.parse.unquote('$AZDO_PROJECT_URL_SAFE'))")
193
193
 
194
194
  if [ -z "$AZDO_ORG" ] || [ -z "$AZDO_PROJECT" ]; then
195
195
  echo "❌ Could not extract organization/project from remote URL"
@@ -210,7 +210,7 @@ az devops configure --defaults organization="$AZDO_ORG_URL" project="$AZDO_PROJE
210
210
 
211
211
  When `AZDO_MODE == rest`, store these for API calls:
212
212
  - Base URL: `$AZDO_ORG_URL/$AZDO_PROJECT_URL_SAFE/_apis`
213
- - Auth header: `Authorization: Basic $(echo -n ":$PAT" | base64)`
213
+ - Auth header: `Authorization: Basic $(printf ":%s" "$PAT" | base64 | tr -d '\n')`
214
214
 
215
215
  Report in pre-flight:
216
216
  - ✅ `azdo context` — org: `{AZDO_ORG}`, project: `{AZDO_PROJECT}`
@@ -130,7 +130,7 @@ Bad examples:
130
130
 
131
131
  **If PLATFORM == azdo AND AZDO_MODE == rest:**
132
132
  REPO_NAME=$(basename -s .git "$(git remote get-url origin)")
133
- AUTH="Authorization: Basic $(echo -n ":{PAT}" | base64)"
133
+ AUTH="Authorization: Basic $(printf ":%s" "{PAT}" | base64 | tr -d '\n')"
134
134
  PR_JSON=$(curl -s -X POST \
135
135
  -H "$AUTH" \
136
136
  -H "Content-Type: application/json" \
@@ -231,7 +231,7 @@ FAILING CHECKS: {FAILING_CHECKS}
231
231
  rm -rf "$LOGDIR"
232
232
 
233
233
  **If PLATFORM == azdo AND AZDO_MODE == rest:**
234
- AUTH="Authorization: Basic $(echo -n ":{PAT}" | base64)"
234
+ AUTH="Authorization: Basic $(printf ":%s" "{PAT}" | base64 | tr -d '\n')"
235
235
  # Get failed build ID
236
236
  RUN_ID=$(curl -s -H "$AUTH" \
237
237
  "{AZDO_ORG_URL}/{AZDO_PROJECT_URL_SAFE}/_apis/build/builds?branchName=refs/heads/{BRANCH}&resultFilter=failed&\$top=1&api-version=7.0" \
@@ -88,21 +88,31 @@ if [ "$AZDO_MODE" = "rest" ]; then
88
88
  CHECKS="error:no PAT configured"
89
89
  emit_report; exit 3
90
90
  fi
91
- AUTH="Authorization: Basic $(echo -n ":$PAT" | base64)"
91
+ AUTH="Authorization: Basic $(printf ":%s" "$PAT" | base64 | tr -d '\n')"
92
92
  fi
93
93
 
94
94
  # ── AzDO CLI mode ──────────────────────────────────────
95
95
  if [ "$AZDO_MODE" = "cli" ]; then
96
96
  # Grace period: wait for CI to start (max 2 min)
97
+ # Try PR merge ref first, then fall back to branch name
97
98
  RUNS_FOUND=false
99
+ CI_BRANCH=""
98
100
  GRACE_POLLS=0
99
101
  for _ in $(seq 1 8); do
100
102
  GRACE_POLLS=$((GRACE_POLLS + 1))
103
+ # Try PR merge ref first (AzDO sets sourceBranch to refs/pull/<N>/merge)
104
+ RUN_COUNT=$(az pipelines runs list --branch "refs/pull/$PR_NUMBER/merge" --top 5 \
105
+ --org "$AZDO_ORG_URL" --project "$AZDO_PROJECT" \
106
+ --query "length(@)" -o tsv 2>/dev/null)
107
+ if [ -n "$RUN_COUNT" ] && [ "$RUN_COUNT" != "0" ]; then
108
+ RUNS_FOUND=true; CI_BRANCH="refs/pull/$PR_NUMBER/merge"; break
109
+ fi
110
+ # Fallback: try branch name directly
101
111
  RUN_COUNT=$(az pipelines runs list --branch "$BRANCH" --top 5 \
102
112
  --org "$AZDO_ORG_URL" --project "$AZDO_PROJECT" \
103
113
  --query "length(@)" -o tsv 2>/dev/null)
104
114
  if [ -n "$RUN_COUNT" ] && [ "$RUN_COUNT" != "0" ]; then
105
- RUNS_FOUND=true; break
115
+ RUNS_FOUND=true; CI_BRANCH="$BRANCH"; break
106
116
  fi
107
117
  sleep 15
108
118
  done
@@ -120,14 +130,14 @@ if [ "$AZDO_MODE" = "cli" ]; then
120
130
 
121
131
  # Wait for runs to complete with fail-fast (max 30 min)
122
132
  for _ in $(seq 1 120); do
123
- FAILED=$(az pipelines runs list --branch "$BRANCH" --top 5 \
133
+ FAILED=$(az pipelines runs list --branch "$CI_BRANCH" --top 5 \
124
134
  --org "$AZDO_ORG_URL" --project "$AZDO_PROJECT" \
125
135
  --query "[?result=='failed'] | length(@)" -o tsv 2>/dev/null)
126
136
  if [ -n "$FAILED" ] && [ "$FAILED" != "0" ]; then
127
137
  break
128
138
  fi
129
139
 
130
- IN_PROGRESS=$(az pipelines runs list --branch "$BRANCH" --top 5 \
140
+ IN_PROGRESS=$(az pipelines runs list --branch "$CI_BRANCH" --top 5 \
131
141
  --org "$AZDO_ORG_URL" --project "$AZDO_PROJECT" \
132
142
  --query "[?status=='inProgress'] | length(@)" -o tsv 2>/dev/null)
133
143
  if [ "$IN_PROGRESS" = "0" ] || [ -z "$IN_PROGRESS" ]; then break; fi
@@ -135,7 +145,7 @@ if [ "$AZDO_MODE" = "cli" ]; then
135
145
  done
136
146
 
137
147
  # Determine final status
138
- RUNS_TABLE=$(az pipelines runs list --branch "$BRANCH" --top 5 \
148
+ RUNS_TABLE=$(az pipelines runs list --branch "$CI_BRANCH" --top 5 \
139
149
  --org "$AZDO_ORG_URL" --project "$AZDO_PROJECT" \
140
150
  --query "[].{name:definition.name, result:result}" -o json 2>/dev/null || echo "[]")
141
151
  CHECKS=$(echo "$RUNS_TABLE" | jq -r '.[] | "\(.name):\(.result // "pending")"' | paste -sd, -)
@@ -163,33 +173,64 @@ fi
163
173
 
164
174
  # ── AzDO REST mode ─────────────────────────────────────
165
175
  if [ "$AZDO_MODE" = "rest" ]; then
166
- BUILDS_URL="$AZDO_ORG_URL/$AZDO_PROJECT_URL_SAFE/_apis/build/builds?branchName=refs/heads/$BRANCH&\$top=5&api-version=7.0"
176
+ BUILDS_BASE="$AZDO_ORG_URL/$AZDO_PROJECT_URL_SAFE/_apis/build/builds"
167
177
 
168
178
  # Grace period: wait for CI to start (max 2 min)
179
+ # Try PR merge ref first, then fall back to branch name
169
180
  RUNS_FOUND=false
181
+ BUILDS_URL=""
170
182
  GRACE_POLLS=0
171
183
  for _ in $(seq 1 8); do
172
184
  GRACE_POLLS=$((GRACE_POLLS + 1))
173
- RUN_COUNT=$(curl -s -H "$AUTH" "$BUILDS_URL" | jq '.value | length')
185
+ # Try PR merge ref first (AzDO sets sourceBranch to refs/pull/<N>/merge)
186
+ PR_BUILDS_URL="${BUILDS_BASE}?branchName=refs/pull/$PR_NUMBER/merge&\$top=5&api-version=7.0"
187
+ RESPONSE=$(curl -s -H "$AUTH" "$PR_BUILDS_URL" 2>&1)
188
+ if ! echo "$RESPONSE" | jq empty 2>/dev/null; then
189
+ STATUS="no_checks"; FAILING="none"; CHECKS="error:non-JSON API response"
190
+ emit_report; exit 3
191
+ fi
192
+ RUN_COUNT=$(echo "$RESPONSE" | jq '.value | length')
193
+ if [ -n "$RUN_COUNT" ] && [ "$RUN_COUNT" != "0" ]; then
194
+ RUNS_FOUND=true; BUILDS_URL="$PR_BUILDS_URL"; break
195
+ fi
196
+ # Fallback: try branch name directly
197
+ BRANCH_BUILDS_URL="${BUILDS_BASE}?branchName=refs/heads/$BRANCH&\$top=5&api-version=7.0"
198
+ RESPONSE=$(curl -s -H "$AUTH" "$BRANCH_BUILDS_URL" 2>&1)
199
+ if ! echo "$RESPONSE" | jq empty 2>/dev/null; then
200
+ STATUS="no_checks"; FAILING="none"; CHECKS="error:non-JSON API response"
201
+ emit_report; exit 3
202
+ fi
203
+ RUN_COUNT=$(echo "$RESPONSE" | jq '.value | length')
174
204
  if [ -n "$RUN_COUNT" ] && [ "$RUN_COUNT" != "0" ]; then
175
- RUNS_FOUND=true; break
205
+ RUNS_FOUND=true; BUILDS_URL="$BRANCH_BUILDS_URL"; break
176
206
  fi
177
207
  sleep 15
178
208
  done
179
209
 
180
210
  if [ "$RUNS_FOUND" = false ]; then
181
- EVALS=$(curl -s -H "$AUTH" \
182
- "$AZDO_ORG_URL/$AZDO_PROJECT_URL_SAFE/_apis/policy/evaluations?artifactId=vstfs:///CodeReview/CodeReviewId/$AZDO_PROJECT_URL_SAFE/$PR_NUMBER&api-version=7.0")
183
- EVAL_COUNT=$(echo "$EVALS" | jq '.value | length')
211
+ RESPONSE=$(curl -s -H "$AUTH" \
212
+ "$AZDO_ORG_URL/$AZDO_PROJECT_URL_SAFE/_apis/policy/evaluations?artifactId=vstfs:///CodeReview/CodeReviewId/$AZDO_PROJECT_URL_SAFE/$PR_NUMBER&api-version=7.0" 2>&1)
213
+ if ! echo "$RESPONSE" | jq empty 2>/dev/null; then
214
+ STATUS="no_checks"; FAILING="none"; CHECKS="error:non-JSON API response"
215
+ emit_report; exit 3
216
+ fi
217
+ EVAL_COUNT=$(echo "$RESPONSE" | jq '.value | length')
184
218
  if [ "$EVAL_COUNT" = "0" ] || [ -z "$EVAL_COUNT" ]; then
185
219
  STATUS="no_checks"; CHECKS=""; FAILING="none"
186
220
  emit_report; exit 0
187
221
  fi
222
+ # Default BUILDS_URL for subsequent polling if policies exist but no runs yet
223
+ BUILDS_URL="${BUILDS_BASE}?branchName=refs/pull/$PR_NUMBER/merge&\$top=5&api-version=7.0"
188
224
  fi
189
225
 
190
226
  # Wait for runs with fail-fast (max 30 min)
191
227
  for _ in $(seq 1 120); do
192
- BUILDS_JSON=$(curl -s -H "$AUTH" "$BUILDS_URL")
228
+ RESPONSE=$(curl -s -H "$AUTH" "$BUILDS_URL" 2>&1)
229
+ if ! echo "$RESPONSE" | jq empty 2>/dev/null; then
230
+ STATUS="no_checks"; FAILING="none"; CHECKS="error:non-JSON API response"
231
+ emit_report; exit 3
232
+ fi
233
+ BUILDS_JSON="$RESPONSE"
193
234
 
194
235
  FAIL_COUNT=$(echo "$BUILDS_JSON" | jq '[.value[] | select(.result=="failed")] | length')
195
236
  if [ "${FAIL_COUNT:-0}" -gt 0 ]; then break; fi
@@ -200,16 +241,25 @@ if [ "$AZDO_MODE" = "rest" ]; then
200
241
  done
201
242
 
202
243
  # Final status
203
- BUILDS_JSON=$(curl -s -H "$AUTH" "$BUILDS_URL")
244
+ RESPONSE=$(curl -s -H "$AUTH" "$BUILDS_URL" 2>&1)
245
+ if ! echo "$RESPONSE" | jq empty 2>/dev/null; then
246
+ STATUS="no_checks"; FAILING="none"; CHECKS="error:non-JSON API response"
247
+ emit_report; exit 3
248
+ fi
249
+ BUILDS_JSON="$RESPONSE"
204
250
  CHECKS=$(echo "$BUILDS_JSON" | jq -r '.value[] | "\(.definition.name):\(.result // "pending")"' | paste -sd, -)
205
251
  FAILING=$(echo "$BUILDS_JSON" | jq -r '.value[] | select(.result=="failed") | .definition.name' | paste -sd, -)
206
252
  FAIL_COUNT=$(echo "$BUILDS_JSON" | jq '[.value[] | select(.result=="failed")] | length')
207
253
  STILL_RUNNING=$(echo "$BUILDS_JSON" | jq '[.value[] | select(.status=="inProgress")] | length')
208
254
 
209
255
  # Check policy evaluations
210
- EVALS=$(curl -s -H "$AUTH" \
211
- "$AZDO_ORG_URL/$AZDO_PROJECT_URL_SAFE/_apis/policy/evaluations?artifactId=vstfs:///CodeReview/CodeReviewId/$AZDO_PROJECT_URL_SAFE/$PR_NUMBER&api-version=7.0")
212
- REJECTED=$(echo "$EVALS" | jq '[.value[] | select(.status=="rejected")] | length')
256
+ RESPONSE=$(curl -s -H "$AUTH" \
257
+ "$AZDO_ORG_URL/$AZDO_PROJECT_URL_SAFE/_apis/policy/evaluations?artifactId=vstfs:///CodeReview/CodeReviewId/$AZDO_PROJECT_URL_SAFE/$PR_NUMBER&api-version=7.0" 2>&1)
258
+ if ! echo "$RESPONSE" | jq empty 2>/dev/null; then
259
+ REJECTED="0"
260
+ else
261
+ REJECTED=$(echo "$RESPONSE" | jq '[.value[] | select(.status=="rejected")] | length')
262
+ fi
213
263
 
214
264
  if [ "${FAIL_COUNT:-0}" -gt 0 ] || [ "${REJECTED:-0}" -gt 0 ]; then
215
265
  STATUS="some_failed"
@@ -96,7 +96,7 @@ if [ "$AZDO_MODE" = "cli" ]; then
96
96
 
97
97
  # Complete the PR
98
98
  if az repos pr update --id "$PR_NUMBER" --status completed \
99
- --org "$AZDO_ORG_URL" --project "$AZDO_PROJECT" \
99
+ --org "$AZDO_ORG_URL" \
100
100
  --squash "$SQUASH_FLAG" \
101
101
  --delete-source-branch "$DELETE_FLAG" 2>/dev/null; then
102
102
  STATUS="success"
@@ -106,7 +106,7 @@ if [ "$AZDO_MODE" = "cli" ]; then
106
106
  # Retry once after 30s (policies may still be evaluating)
107
107
  sleep 30
108
108
  if az repos pr update --id "$PR_NUMBER" --status completed \
109
- --org "$AZDO_ORG_URL" --project "$AZDO_PROJECT" \
109
+ --org "$AZDO_ORG_URL" \
110
110
  --squash "$SQUASH_FLAG" \
111
111
  --delete-source-branch "$DELETE_FLAG" 2>/dev/null; then
112
112
  STATUS="success"
@@ -125,7 +125,7 @@ fi
125
125
 
126
126
  # ── AzDO REST mode ─────────────────────────────────────
127
127
  if [ "$AZDO_MODE" = "rest" ]; then
128
- AUTH="Authorization: Basic $(echo -n ":$PAT" | base64)"
128
+ AUTH="Authorization: Basic $(printf ":%s" "$PAT" | base64 | tr -d '\n')"
129
129
  REPO_NAME=$(basename -s .git "$(git remote get-url origin)")
130
130
  PR_API="$AZDO_ORG_URL/$AZDO_PROJECT_URL_SAFE/_apis/git/repositories/$REPO_NAME/pullrequests/$PR_NUMBER"
131
131
 
@@ -133,7 +133,10 @@ if [ "$AZDO_MODE" = "rest" ]; then
133
133
  POLICY_TIMEOUT=20
134
134
  for POLICY_ITER in $(seq 1 $POLICY_TIMEOUT); do
135
135
  EVALS=$(curl -s -H "$AUTH" \
136
- "$AZDO_ORG_URL/$AZDO_PROJECT_URL_SAFE/_apis/policy/evaluations?artifactId=vstfs:///CodeReview/CodeReviewId/$AZDO_PROJECT_URL_SAFE/$PR_NUMBER&api-version=7.0" 2>/dev/null || echo '{"value":[]}')
136
+ "$AZDO_ORG_URL/$AZDO_PROJECT_URL_SAFE/_apis/policy/evaluations?artifactId=vstfs:///CodeReview/CodeReviewId/$AZDO_PROJECT_URL_SAFE/$PR_NUMBER&api-version=7.0" 2>&1)
137
+ if ! echo "$EVALS" | jq empty 2>/dev/null; then
138
+ EVALS='{"value":[]}'
139
+ fi
137
140
 
138
141
  REJECTED=$(echo "$EVALS" | jq '[.value[] | select(.status=="rejected")] | length')
139
142
  PENDING=$(echo "$EVALS" | jq '[.value[] | select(.status=="running" or .status=="queued" or .status=="pending")] | length')
@@ -165,7 +168,13 @@ if [ "$AZDO_MODE" = "rest" ]; then
165
168
  # Complete the PR
166
169
  RESPONSE=$(curl -s -X PATCH -H "$AUTH" -H "Content-Type: application/json" \
167
170
  "$PR_API?api-version=7.0" \
168
- -d "{\"status\": \"completed\", \"completionOptions\": {\"mergeStrategy\": \"$MERGE_STRATEGY\", \"deleteSourceBranch\": $DELETE_FLAG}}")
171
+ -d "{\"status\": \"completed\", \"completionOptions\": {\"mergeStrategy\": \"$MERGE_STRATEGY\", \"deleteSourceBranch\": $DELETE_FLAG}}" 2>&1)
172
+ if ! echo "$RESPONSE" | jq empty 2>/dev/null; then
173
+ STATUS="failed"
174
+ ERRORS="REST merge returned non-JSON response"
175
+ MERGE_COMMIT=""; BRANCH_DELETED=false
176
+ emit_report; exit 1
177
+ fi
169
178
 
170
179
  PR_STATUS=$(echo "$RESPONSE" | jq -r '.status // empty')
171
180
  if [ "$PR_STATUS" = "completed" ]; then
@@ -1,88 +0,0 @@
1
- # Azure DevOps Platform Support
2
-
3
- Shared procedures for AzDO tooling detection and configuration validation.
4
- Referenced by SKILL.md during pre-flight when `PLATFORM == azdo`.
5
-
6
- ## AzDO Tooling Detection
7
-
8
- When `PLATFORM == azdo`, determine which tooling is available. Set `AZDO_MODE`
9
- for use in all subsequent phases:
10
-
11
- ```bash
12
- if az devops -h &>/dev/null; then
13
- AZDO_MODE="cli"
14
- else
15
- AZDO_MODE="rest"
16
- fi
17
- ```
18
-
19
- - **`cli`**: Use `az repos` / `az pipelines` commands (preferred)
20
- - **`rest`**: Use Azure DevOps REST API via `curl`. Requires a PAT (personal
21
- access token) in `AZURE_DEVOPS_EXT_PAT` or `AZDO_PAT` env var. If no PAT
22
- is found, prompt the user to either install the CLI or set the env var.
23
-
24
- Report in pre-flight:
25
- - ✅ `az devops cli` — version found
26
- - ⚠️ `az devops cli` — not found → using REST API fallback
27
- - ❌ `az devops cli` — not found, no PAT configured → halt with setup instructions
28
-
29
- ## AzDO Configuration Validation
30
-
31
- When `PLATFORM == azdo`, extract organization and project from the remote URL
32
- and validate they are usable. These values are needed by every `az repos` /
33
- `az pipelines` command and every REST API call.
34
-
35
- ```bash
36
- # Extract org and project from remote URL patterns:
37
- # https://dev.azure.com/{ORG}/{PROJECT}/_git/{REPO}
38
- # https://{ORG}@dev.azure.com/{ORG}/{PROJECT}/_git/{REPO}
39
- # {ORG}@vs-ssh.visualstudio.com:v3/{ORG}/{PROJECT}/{REPO}
40
-
41
- REMOTE_URL=$(git remote get-url origin 2>/dev/null)
42
-
43
- if echo "$REMOTE_URL" | grep -q 'dev\.azure\.com'; then
44
- AZDO_ORG=$(echo "$REMOTE_URL" | sed -n 's|.*dev\.azure\.com/\([^/]*\)/.*|\1|p')
45
- AZDO_PROJECT=$(echo "$REMOTE_URL" | sed -n 's|.*dev\.azure\.com/[^/]*/\([^/]*\)/.*|\1|p')
46
- AZDO_ORG_URL="https://dev.azure.com/$AZDO_ORG"
47
- elif echo "$REMOTE_URL" | grep -q 'vs-ssh\.visualstudio\.com'; then
48
- AZDO_ORG=$(echo "$REMOTE_URL" | sed -n 's|.*vs-ssh\.visualstudio\.com:v3/\([^/]*\)/.*|\1|p')
49
- AZDO_PROJECT=$(echo "$REMOTE_URL" | sed -n 's|.*vs-ssh\.visualstudio\.com:v3/[^/]*/\([^/]*\)/.*|\1|p')
50
- AZDO_ORG_URL="https://dev.azure.com/$AZDO_ORG"
51
- elif echo "$REMOTE_URL" | grep -q 'visualstudio\.com'; then
52
- AZDO_ORG=$(echo "$REMOTE_URL" | sed -n 's|.*//\([^.]*\)\.visualstudio\.com.*|\1|p')
53
- AZDO_PROJECT=$(echo "$REMOTE_URL" | sed -n 's|.*/\([^/]*\)/_git/.*|\1|p')
54
- AZDO_ORG_URL="https://dev.azure.com/$AZDO_ORG"
55
- fi
56
-
57
- # URL-decode for CLI/display; keep URL-safe versions for REST API paths
58
- AZDO_PROJECT_URL_SAFE="$AZDO_PROJECT"
59
- AZDO_ORG=$(printf '%b' "${AZDO_ORG//%/\\x}")
60
- AZDO_PROJECT=$(printf '%b' "${AZDO_PROJECT//%/\\x}")
61
-
62
- if [ -z "$AZDO_ORG" ] || [ -z "$AZDO_PROJECT" ]; then
63
- echo "❌ Could not extract organization/project from remote URL"
64
- echo " Remote: $REMOTE_URL"
65
- echo ""
66
- echo "Ensure the remote URL follows one of these formats:"
67
- echo " https://dev.azure.com/{ORG}/{PROJECT}/_git/{REPO}"
68
- echo " https://{ORG}.visualstudio.com/{PROJECT}/_git/{REPO}"
69
- echo " {ORG}@vs-ssh.visualstudio.com:v3/{ORG}/{PROJECT}/{REPO}"
70
- # HALT — cannot proceed without org/project context
71
- fi
72
- ```
73
-
74
- When `AZDO_MODE == cli`, also configure the defaults so commands work correctly:
75
- ```bash
76
- az devops configure --defaults organization="$AZDO_ORG_URL" project="$AZDO_PROJECT"
77
- ```
78
-
79
- When `AZDO_MODE == rest`, store these for API calls:
80
- - Base URL: `$AZDO_ORG_URL/$AZDO_PROJECT_URL_SAFE/_apis`
81
- - Auth header: `Authorization: Basic $(echo -n ":$PAT" | base64)`
82
-
83
- Report in pre-flight:
84
- - ✅ `azdo context` — org: `{AZDO_ORG}`, project: `{AZDO_PROJECT}`
85
- - ❌ `azdo context` — could not parse from remote URL → halt with instructions
86
-
87
- Pass `{AZDO_MODE}`, `{AZDO_ORG}`, `{AZDO_PROJECT}`, `{AZDO_ORG_URL}` into
88
- all phase prompts alongside `{PLATFORM}`.
@@ -1,88 +0,0 @@
1
- # Azure DevOps Platform Support
2
-
3
- Shared procedures for AzDO tooling detection and configuration validation.
4
- Referenced by SKILL.md during pre-flight when `PLATFORM == azdo`.
5
-
6
- ## AzDO Tooling Detection
7
-
8
- When `PLATFORM == azdo`, determine which tooling is available. Set `AZDO_MODE`
9
- for use in all subsequent phases:
10
-
11
- ```bash
12
- if az devops -h &>/dev/null; then
13
- AZDO_MODE="cli"
14
- else
15
- AZDO_MODE="rest"
16
- fi
17
- ```
18
-
19
- - **`cli`**: Use `az repos` / `az pipelines` commands (preferred)
20
- - **`rest`**: Use Azure DevOps REST API via `curl`. Requires a PAT (personal
21
- access token) in `AZURE_DEVOPS_EXT_PAT` or `AZDO_PAT` env var. If no PAT
22
- is found, prompt the user to either install the CLI or set the env var.
23
-
24
- Report in pre-flight:
25
- - ✅ `az devops cli` — version found
26
- - ⚠️ `az devops cli` — not found → using REST API fallback
27
- - ❌ `az devops cli` — not found, no PAT configured → halt with setup instructions
28
-
29
- ## AzDO Configuration Validation
30
-
31
- When `PLATFORM == azdo`, extract organization and project from the remote URL
32
- and validate they are usable. These values are needed by every `az repos` /
33
- `az pipelines` command and every REST API call.
34
-
35
- ```bash
36
- # Extract org and project from remote URL patterns:
37
- # https://dev.azure.com/{ORG}/{PROJECT}/_git/{REPO}
38
- # https://{ORG}@dev.azure.com/{ORG}/{PROJECT}/_git/{REPO}
39
- # {ORG}@vs-ssh.visualstudio.com:v3/{ORG}/{PROJECT}/{REPO}
40
-
41
- REMOTE_URL=$(git remote get-url origin 2>/dev/null)
42
-
43
- if echo "$REMOTE_URL" | grep -q 'dev\.azure\.com'; then
44
- AZDO_ORG=$(echo "$REMOTE_URL" | sed -n 's|.*dev\.azure\.com/\([^/]*\)/.*|\1|p')
45
- AZDO_PROJECT=$(echo "$REMOTE_URL" | sed -n 's|.*dev\.azure\.com/[^/]*/\([^/]*\)/.*|\1|p')
46
- AZDO_ORG_URL="https://dev.azure.com/$AZDO_ORG"
47
- elif echo "$REMOTE_URL" | grep -q 'vs-ssh\.visualstudio\.com'; then
48
- AZDO_ORG=$(echo "$REMOTE_URL" | sed -n 's|.*vs-ssh\.visualstudio\.com:v3/\([^/]*\)/.*|\1|p')
49
- AZDO_PROJECT=$(echo "$REMOTE_URL" | sed -n 's|.*vs-ssh\.visualstudio\.com:v3/[^/]*/\([^/]*\)/.*|\1|p')
50
- AZDO_ORG_URL="https://dev.azure.com/$AZDO_ORG"
51
- elif echo "$REMOTE_URL" | grep -q 'visualstudio\.com'; then
52
- AZDO_ORG=$(echo "$REMOTE_URL" | sed -n 's|.*//\([^.]*\)\.visualstudio\.com.*|\1|p')
53
- AZDO_PROJECT=$(echo "$REMOTE_URL" | sed -n 's|.*/\([^/]*\)/_git/.*|\1|p')
54
- AZDO_ORG_URL="https://dev.azure.com/$AZDO_ORG"
55
- fi
56
-
57
- # URL-decode for CLI/display; keep URL-safe versions for REST API paths
58
- AZDO_PROJECT_URL_SAFE="$AZDO_PROJECT"
59
- AZDO_ORG=$(printf '%b' "${AZDO_ORG//%/\\x}")
60
- AZDO_PROJECT=$(printf '%b' "${AZDO_PROJECT//%/\\x}")
61
-
62
- if [ -z "$AZDO_ORG" ] || [ -z "$AZDO_PROJECT" ]; then
63
- echo "❌ Could not extract organization/project from remote URL"
64
- echo " Remote: $REMOTE_URL"
65
- echo ""
66
- echo "Ensure the remote URL follows one of these formats:"
67
- echo " https://dev.azure.com/{ORG}/{PROJECT}/_git/{REPO}"
68
- echo " https://{ORG}.visualstudio.com/{PROJECT}/_git/{REPO}"
69
- echo " {ORG}@vs-ssh.visualstudio.com:v3/{ORG}/{PROJECT}/{REPO}"
70
- # HALT — cannot proceed without org/project context
71
- fi
72
- ```
73
-
74
- When `AZDO_MODE == cli`, also configure the defaults so commands work correctly:
75
- ```bash
76
- az devops configure --defaults organization="$AZDO_ORG_URL" project="$AZDO_PROJECT"
77
- ```
78
-
79
- When `AZDO_MODE == rest`, store these for API calls:
80
- - Base URL: `$AZDO_ORG_URL/$AZDO_PROJECT_URL_SAFE/_apis`
81
- - Auth header: `Authorization: Basic $(echo -n ":$PAT" | base64)`
82
-
83
- Report in pre-flight:
84
- - ✅ `azdo context` — org: `{AZDO_ORG}`, project: `{AZDO_PROJECT}`
85
- - ❌ `azdo context` — could not parse from remote URL → halt with instructions
86
-
87
- Pass `{AZDO_MODE}`, `{AZDO_ORG}`, `{AZDO_PROJECT}`, `{AZDO_ORG_URL}` into
88
- all phase prompts alongside `{PLATFORM}`.