@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.
- package/.claude-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/skills/brace/SKILL.md +4 -4
- package/skills/dock/SKILL.md +3 -2
- package/skills/keel/SKILL.md +3 -2
- package/skills/manifest.json +3 -3
- package/skills/rig/SKILL.md +4 -4
- package/skills/ship/SKILL.md +4 -4
- package/skills/ship/references/stage-prompts.md +2 -2
- package/skills/ship/scripts/ci-watch.sh +66 -16
- package/skills/ship/scripts/merge.sh +14 -5
- package/skills/dock/references/azdo-platform.md +0 -88
- package/skills/keel/references/azdo-platform.md +0 -88
package/package.json
CHANGED
package/skills/brace/SKILL.md
CHANGED
|
@@ -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
|
|
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=$(
|
|
350
|
-
AZDO_PROJECT=$(
|
|
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 $(
|
|
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" \
|
package/skills/dock/SKILL.md
CHANGED
|
@@ -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
|
|
132
|
-
|
|
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
|
---
|
package/skills/keel/SKILL.md
CHANGED
|
@@ -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
|
|
128
|
-
|
|
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
|
---
|
package/skills/manifest.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generated": "2026-03-
|
|
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":
|
|
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":
|
|
49
|
+
"lines": 420,
|
|
50
50
|
"hasScripts": false,
|
|
51
51
|
"hasReferences": true,
|
|
52
52
|
"hasAssets": false,
|
package/skills/rig/SKILL.md
CHANGED
|
@@ -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
|
|
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=$(
|
|
175
|
-
AZDO_PROJECT=$(
|
|
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 $(
|
|
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}`
|
package/skills/ship/SKILL.md
CHANGED
|
@@ -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
|
|
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=$(
|
|
192
|
-
AZDO_PROJECT=$(
|
|
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 $(
|
|
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 $(
|
|
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 $(
|
|
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 $(
|
|
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 "$
|
|
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 "$
|
|
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 "$
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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"
|
|
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"
|
|
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 $(
|
|
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
|
|
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}`.
|