test-skill-structure.sh 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. #!/usr/bin/env bash
  2. # Structural checks for skills/diagnosing-superpowers. Behavior is tested by
  3. # scenario evals kept by the maintainer; this script only checks the things a
  4. # shell can check: frontmatter, referenced files exist, no local paths or
  5. # names leaked into shipped files, SKILL.md word budget.
  6. set -u
  7. SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
  8. REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
  9. SKILL_DIR="$REPO_ROOT/skills/diagnosing-superpowers"
  10. SKILL_MD="$SKILL_DIR/SKILL.md"
  11. WORD_BUDGET=1000
  12. PASSES=0
  13. FAILURES=0
  14. pass() { echo " [PASS] $1"; PASSES=$((PASSES + 1)); }
  15. fail() { echo " [FAIL] $1"; FAILURES=$((FAILURES + 1)); }
  16. echo "diagnosing-superpowers structure"
  17. # --- SKILL.md frontmatter -------------------------------------------------
  18. if [ -f "$SKILL_MD" ]; then
  19. pass "SKILL.md exists"
  20. frontmatter="$(awk 'NR==1 && $0!="---"{exit} NR>1 && $0=="---"{exit} NR>1{print}' "$SKILL_MD")"
  21. if printf '%s\n' "$frontmatter" | grep -q '^name: diagnosing-superpowers$'; then
  22. pass "frontmatter name is diagnosing-superpowers"
  23. else
  24. fail "frontmatter name is diagnosing-superpowers"
  25. fi
  26. description="$(printf '%s\n' "$frontmatter" | awk '/^description:/{sub(/^description:[ ]*/,""); print; found=1; next} found && /^[ ]/{print} found && !/^[ ]/{exit}' | tr '\n' ' ')"
  27. if printf '%s' "$description" | grep -q '^Use when'; then
  28. pass "description starts with 'Use when'"
  29. else
  30. fail "description starts with 'Use when' (got: ${description:0:60})"
  31. fi
  32. if [ "${#description}" -le 1024 ]; then
  33. pass "description under 1024 characters"
  34. else
  35. fail "description under 1024 characters (${#description})"
  36. fi
  37. for banned in "dispatch" "then" "step"; do
  38. if printf '%s' "$description" | grep -qiw "$banned"; then
  39. fail "description contains workflow word '$banned'"
  40. else
  41. pass "description avoids workflow word '$banned'"
  42. fi
  43. done
  44. # --- word budget --------------------------------------------------------
  45. body_words="$(awk 'BEGIN{fm=0} NR==1 && $0=="---"{fm=1; next} fm==1 && $0=="---"{fm=2; next} fm==2{print}' "$SKILL_MD" | wc -w | tr -d ' ')"
  46. if [ "$body_words" -le "$WORD_BUDGET" ]; then
  47. pass "SKILL.md body within $WORD_BUDGET words ($body_words)"
  48. else
  49. fail "SKILL.md body within $WORD_BUDGET words ($body_words)"
  50. fi
  51. # --- required sections --------------------------------------------------
  52. for heading in "## Hard rules" "## Red Flags"; do
  53. if grep -q "^$heading" "$SKILL_MD"; then
  54. pass "SKILL.md has section '$heading'"
  55. else
  56. fail "SKILL.md has section '$heading'"
  57. fi
  58. done
  59. # --- every referenced skill file exists --------------------------------
  60. while IFS= read -r ref; do
  61. if [ -f "$SKILL_DIR/$ref" ]; then
  62. pass "referenced file exists: $ref"
  63. else
  64. fail "referenced file exists: $ref"
  65. fi
  66. done < <(grep -o '\(references\|prompts\|templates\)/[A-Za-z0-9._-]*\.md' "$SKILL_MD" | sort -u)
  67. else
  68. fail "SKILL.md exists"
  69. fi
  70. # --- expected files -------------------------------------------------------
  71. expected_files=(
  72. references/redaction-policy.md
  73. references/session-discovery.md
  74. references/context-safety.md
  75. references/github-issues.md
  76. prompts/analyst-common.md
  77. prompts/skill-timeline.md
  78. prompts/plan-adherence.md
  79. prompts/repeated-work.md
  80. prompts/stumbles.md
  81. prompts/quality-evidence.md
  82. prompts/request-conflicts.md
  83. prompts/cost-and-time.md
  84. prompts/scrub.md
  85. prompts/scrub-audit.md
  86. prompts/similar-session.md
  87. templates/case.md
  88. templates/report.md
  89. templates/bundle-README.md
  90. templates/issue.md
  91. )
  92. for rel in "${expected_files[@]}"; do
  93. if [ -f "$SKILL_DIR/$rel" ]; then
  94. pass "expected file present: $rel"
  95. else
  96. fail "expected file present: $rel"
  97. fi
  98. done
  99. # --- removed harness recipes stay removed --------------------------------
  100. removed_files=(
  101. references/claude-code-sessions.md
  102. references/codex-sessions.md
  103. references/other-harnesses.md
  104. )
  105. for rel in "${removed_files[@]}"; do
  106. if [ ! -e "$SKILL_DIR/$rel" ]; then
  107. pass "removed reference absent: $rel"
  108. else
  109. fail "removed reference absent: $rel"
  110. fi
  111. done
  112. removed_reference_hits="$(grep -rn -E 'references/(claude-code-sessions|codex-sessions|other-harnesses)\.md' "$SKILL_DIR" --include='*.md' 2>/dev/null || true)"
  113. if [ -z "$removed_reference_hits" ]; then
  114. pass "active skill prose has no references to removed harness recipes"
  115. else
  116. fail "active skill prose has no references to removed harness recipes"
  117. printf '%s\n' "$removed_reference_hits" | head -10 | sed 's/^/ /'
  118. fi
  119. # --- no local paths or names in shipped files ----------------------------
  120. leaks="$(grep -rn -E '/Users/|/home/|jesse' "$SKILL_DIR" "$SCRIPT_DIR" --exclude=test-skill-structure.sh 2>/dev/null || true)"
  121. if [ -z "$leaks" ]; then
  122. pass "no machine-specific paths or names in shipped files (skills + tests)"
  123. else
  124. fail "no machine-specific paths or names in shipped files (skills + tests)"
  125. printf '%s\n' "$leaks" | head -10 | sed 's/^/ /'
  126. fi
  127. # --- "the user" never appears in skill prose -----------------------------
  128. user_hits="$(grep -rn -i 'the user' "$SKILL_DIR" --include='*.md' 2>/dev/null || true)"
  129. if [ -z "$user_hits" ]; then
  130. pass "skill files say 'your human partner', not 'the user'"
  131. else
  132. fail "skill files say 'your human partner', not 'the user'"
  133. printf '%s\n' "$user_hits" | head -10 | sed 's/^/ /'
  134. fi
  135. echo
  136. echo "Passed: $PASSES Failed: $FAILURES"
  137. [ "$FAILURES" -eq 0 ]