Workshop Review - AWS Workshop Studio Audit
Overview
This skill performs a comprehensive audit of an AWS Workshop Studio workshop (Hugo-based, bilingual EN/ZH) by:
- Verifying directory structure, frontmatter, and weight ordering
- Checking bilingual (EN/ZH) content parity
- Auditing static asset hygiene (unused, broken, oversized)
- Validating internal/external link integrity
- Evaluating content quality (TODOs, alert syntax, service naming, code blocks)
- Checking navigation flow (weight order, next links, lab numbering)
When to Use
Trigger /workshop-review when:
- You have finished writing or updating workshop content and want to audit before publishing
- You want to verify bilingual consistency after translation
- You need to check for broken references or unused assets
- Before
git push to Workshop Studio to ensure quality
Usage
/workshop-review # Review current directory
/workshop-review ~/codes/workshop/hr-quicksuite # Review specific workshop
/workshop-review --phase=assets # Run only one phase
/workshop-review --fix # Auto-fix simple issues
Flag behavior
- No flags: Run all 7 phases
- `--phase=<name>`: Run only the named phase (structure, bilingual, assets, links, quality, navigation) plus Phase 7 (report)
- `--fix`: Run all phases, then auto-fix simple issues (missing language specifiers, trailing whitespace, common service name typos)
Workflow
Workshop Directory
|
/workshop-review [path]
|
+----------------------------------+
| Phase 1: Structure & Frontmatter |
| - EN/ZH file pairs |
| - Frontmatter title/weight |
| - Weight ordering |
| - Orphan directories |
+----------------------------------+
|
+----------------------------------+
| Phase 2: Bilingual Consistency |
| - Heading count parity |
| - Image reference parity |
| - Alert count parity |
| - Code block parity |
+----------------------------------+
|
+----------------------------------+
| Phase 3: Asset Hygiene |
| - Referenced vs on-disk images |
| - Unused & broken images |
| - Oversized images (>500KB) |
| - Debug/temp images |
+----------------------------------+
|
+----------------------------------+
| Phase 4: Link Integrity |
| - Internal cross-references |
| - Relative link targets |
| - :assetUrl directive usage |
| - Hardcoded WS URLs |
+----------------------------------+
|
+----------------------------------+
| Phase 5: Content Quality |
| - TODO/FIXME/TBD markers |
| - Empty sections |
| - Alert syntax validation |
| - AWS service name casing |
| - Code block language specs |
| - Markdown table syntax |
+----------------------------------+
|
+----------------------------------+
| Phase 6: Navigation Flow |
| - Weight ordering logic |
| - Next/continue links |
| - Lab numbering sequence |
| - Prerequisite references |
+----------------------------------+
|
+----------------------------------+
| Phase 7: Review Report |
| - Structured summary tables |
| - Severity-tagged issues |
| - Verdict: PASS/WARN/FAIL |
+----------------------------------+
Phase Details
Phase 1: Structure & Frontmatter
Verify every content directory has proper files and frontmatter.
1a. Discover workshop root and content directory:
bash
WORKSHOP_DIR="${1:-.}"
CONTENT_DIR="$WORKSHOP_DIR/content"
# Verify this looks like a workshop
if [ ! -f "$WORKSHOP_DIR/contentspec.yaml" ]; then
echo "WARNING: No contentspec.yaml found. This may not be a Workshop Studio workshop."
fi
# Check what languages are declared
grep -A5 "localeCodes" "$WORKSHOP_DIR/contentspec.yaml" 2>/dev/null
1b. Find all content directories and check for EN/ZH file pairs:
bash
# List all directories under content/
find "$CONTENT_DIR" -type d | sort
# For each directory, check for index.en.md and index.zh.md
for dir in $(find "$CONTENT_DIR" -type d | sort); do
has_en="no"; has_zh="no"
[ -f "$dir/index.en.md" ] && has_en="yes"
[ -f "$dir/index.zh.md" ] && has_zh="yes"
echo "$dir | EN=$has_en | ZH=$has_zh"
done
1c. Verify frontmatter has title and weight:
bash
# Extract frontmatter from all index files
for f in $(find "$CONTENT_DIR" -name "index.en.md" -o -name "index.zh.md" | sort); do
# Extract title and weight from YAML frontmatter
title=$(sed -n '/^---$/,/^---$/{ /^title:/p }' "$f")
weight=$(sed -n '/^---$/,/^---$/{ /^weight:/p }' "$f")
echo "$f | $title | $weight"
done
1d. Check weight ordering within each parent directory:
bash
# For each parent, list children sorted by weight
for parent in $(find "$CONTENT_DIR" -type d | sort); do
children=""
for child in "$parent"/*/; do
[ -f "$child/index.en.md" ] || continue
w=$(sed -n '/^---$/,/^---$/{ s/^weight: *//p }' "$child/index.en.md")
children="$children$w $child\n"
done
[ -n "$children" ] && echo "--- $parent ---" && echo -e "$children" | sort -n
done
1e. Find orphan directories (directories with no index files):
bash
for dir in $(find "$CONTENT_DIR" -type d | sort); do
[ "$dir" = "$CONTENT_DIR" ] && continue
if [ ! -f "$dir/index.en.md" ] && [ ! -f "$dir/index.zh.md" ]; then
# Only flag if directory has no subdirectories with content either
has_child_content=$(find "$dir" -name "index.en.md" -o -name "index.zh.md" | head -1)
[ -z "$has_child_content" ] && echo "ORPHAN: $dir"
fi
done
Checklist:
- [ ] Every content directory has both
index.en.md and index.zh.md - [ ] Every content file has
title in frontmatter - [ ] Every content file has
weight in frontmatter - [ ] Weight values are numeric and create correct ordering
- [ ] No orphan directories exist
- [ ] EN and ZH files have matching
weight values
Phase 2: Bilingual Consistency
Compare EN/ZH file pairs for structural parity.
2a. Compare heading counts:
bash
for en_file in $(find "$CONTENT_DIR" -name "index.en.md" | sort); do
zh_file="${en_file%.en.md}.zh.md"
[ -f "$zh_file" ] || continue
en_headings=$(grep -c "^#" "$en_file")
zh_headings=$(grep -c "^#" "$zh_file")
dir=$(dirname "$en_file" | sed "s|$CONTENT_DIR/||")
echo "$dir | Headings: EN=$en_headings ZH=$zh_headings"
done
2b. Compare image reference counts:
bash
for en_file in $(find "$CONTENT_DIR" -name "index.en.md" | sort); do
zh_file="${en_file%.en.md}.zh.md"
[ -f "$zh_file" ] || continue
en_images=$(grep -co '!\[' "$en_file")
zh_images=$(grep -co '!\[' "$zh_file")
# Also compare actual image paths
en_paths=$(grep -oP '!\[[^\]]*\]\(\K[^)]+' "$en_file" | sort)
zh_paths=$(grep -oP '!\[[^\]]*\]\(\K[^)]+' "$zh_file" | sort)
dir=$(dirname "$en_file" | sed "s|$CONTENT_DIR/||")
if [ "$en_paths" != "$zh_paths" ]; then
echo "MISMATCH $dir | Images: EN=$en_images ZH=$zh_images (different paths)"
else
echo "OK $dir | Images: EN=$en_images ZH=$zh_images"
fi
done
2c. Compare alert counts and types:
bash
for en_file in $(find "$CONTENT_DIR" -name "index.en.md" | sort); do
zh_file="${en_file%.en.md}.zh.md"
[ -f "$zh_file" ] || continue
en_alerts=$(grep -c '::::alert' "$en_file")
zh_alerts=$(grep -c '::::alert' "$zh_file")
# Compare alert types
en_types=$(grep -oP '::::alert\{type="\K[^"]+' "$en_file" | sort)
zh_types=$(grep -oP '::::alert\{type="\K[^"]+' "$zh_file" | sort)
dir=$(dirname "$en_file" | sed "s|$CONTENT_DIR/||")
echo "$dir | Alerts: EN=$en_alerts ZH=$zh_alerts | Types match: $([ "$en_types" = "$zh_types" ] && echo yes || echo NO)"
done
2d. Compare code block counts:
bash
for en_file in $(find "$CONTENT_DIR" -name "index.en.md" | sort); do
zh_file="${en_file%.en.md}.zh.md"
[ -f "$zh_file" ] || continue
en_code=$(grep -c '```' "$en_file")
zh_code=$(grep -c '```' "$zh_file")
dir=$(dirname "$en_file" | sed "s|$CONTENT_DIR/||")
echo "$dir | Code fences: EN=$en_code ZH=$zh_code"
done
2e. Check for missing translations (one language exists but not the other):
bash
# EN exists but no ZH
for f in $(find "$CONTENT_DIR" -name "index.en.md" | sort); do
zh="${f%.en.md}.zh.md"
[ ! -f "$zh" ] && echo "MISSING ZH: $f"
done
# ZH exists but no EN
for f in $(find "$CONTENT_DIR" -name "index.zh.md" | sort); do
en="${f%.zh.md}.en.md"
[ ! -f "$en" ] && echo "MISSING EN: $f"
done
Checklist:
- [ ] Heading counts match between EN and ZH
- [ ] Image references are identical between EN and ZH
- [ ] Alert counts and types match
- [ ] Code block counts match (code should be identical, only surrounding text translated)
- [ ] No missing translations (every EN has a ZH, every ZH has an EN)
Phase 3: Asset Hygiene
Audit images and static assets.
3a. List all images referenced in content files:
bash
# Extract all image paths from markdown files
grep -rhoP '!\[[^\]]*\]\(\K[^)]+' "$CONTENT_DIR" | sort -u > /tmp/ws-review-referenced-images.txt
cat /tmp/ws-review-referenced-images.txt
3b. List all images on disk:
bash
# List all image files in static/images/
find "$WORKSHOP_DIR/static/images" -type f \( -iname "*.png" -o -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.gif" -o -iname "*.svg" -o -iname "*.webp" \) 2>/dev/null | sort > /tmp/ws-review-disk-images.txt
cat /tmp/ws-review-disk-images.txt
3c. Find unused images (on disk but not referenced):
bash
while IFS= read -r img_path; do
# Convert absolute path to /static/images/... reference format
ref="/static/images/$(basename "$img_path")"
# Also check with subdirectory paths
ref_full=$(echo "$img_path" | sed "s|$WORKSHOP_DIR||")
if ! grep -qF "$ref" /tmp/ws-review-referenced-images.txt && \
! grep -qF "$ref_full" /tmp/ws-review-referenced-images.txt && \
! grep -qF "$(basename "$img_path")" /tmp/ws-review-referenced-images.txt; then
echo "UNUSED: $img_path"
fi
done < /tmp/ws-review-disk-images.txt
3d. Find broken references (referenced but file not on disk):
bash
while IFS= read -r ref; do
# Only check /static/ references (not external URLs)
case "$ref" in
/static/*)
full_path="$WORKSHOP_DIR$ref"
[ ! -f "$full_path" ] && echo "BROKEN: $ref"
;;
esac
done < /tmp/ws-review-referenced-images.txt
3e. Check for debug/temporary images:
bash
find "$WORKSHOP_DIR/static/images" -type f \( \
-name "debug-*" -o -name "test-*" -o -name "tmp-*" -o \
-name "temp-*" -o -name "screenshot-*" -o -name "Screen Shot*" -o \
-name "Screenshot*" -o -name "Untitled*" \
\) 2>/dev/null
3f. Check image file sizes (warn if > 500KB):
bash
find "$WORKSHOP_DIR/static/images" -type f -size +500k \( \
-iname "*.png" -o -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.gif" -o -iname "*.svg" -o -iname "*.webp" \
\) -exec ls -lh {} \; 2>/dev/null | awk '{print $5, $NF}'
Checklist:
- [ ] No broken image references
- [ ] No unused images cluttering static/
- [ ] No debug/temporary images
- [ ] No oversized images (>500KB)
- [ ] Image naming follows convention (lowercase, hyphens, descriptive)
Phase 4: Link Integrity
Validate internal and external links.
4a. Check internal cross-references (relative links):
bash
# Extract all markdown links (not images) from content files
grep -rhoP '\[[^\]]+\]\(\K[^)]+' "$CONTENT_DIR" | grep -v '^!' | grep -v '^http' | grep -v '^#' | sort -u > /tmp/ws-review-internal-links.txt