Improve analyze-and-plan skill quality (65% → 94%) #57
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Tessl Skill Review | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| jobs: | |
| tessl-review: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - uses: tesslio/setup-tessl@v2 | |
| - name: Detect changed skills | |
| id: detect | |
| run: | | |
| if [ "${{ github.event_name }}" = "pull_request" ]; then | |
| CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD) | |
| # Build list of all skill directories (parents of SKILL.md) | |
| ALL_SKILL_DIRS=$(find skills -name SKILL.md -exec dirname {} \; | sort) | |
| # For each changed file, find which skill directory it belongs to | |
| DIRS="" | |
| for changed_file in $CHANGED_FILES; do | |
| for skill_dir in $ALL_SKILL_DIRS; do | |
| case "$changed_file" in | |
| "$skill_dir"/*) | |
| # Check not already added | |
| case " $DIRS " in | |
| *" $skill_dir "*) ;; | |
| *) DIRS="$DIRS $skill_dir" ;; | |
| esac | |
| break | |
| ;; | |
| esac | |
| done | |
| done | |
| DIRS=$(echo "$DIRS" | xargs) # trim whitespace | |
| # Check for unmatched changes under skills/ | |
| UNMATCHED=false | |
| for changed_file in $CHANGED_FILES; do | |
| case "$changed_file" in | |
| skills/*) | |
| MATCHED=false | |
| for skill_dir in $ALL_SKILL_DIRS; do | |
| case "$changed_file" in | |
| "$skill_dir"/*) MATCHED=true; break ;; | |
| esac | |
| done | |
| if [ "$MATCHED" = "false" ]; then | |
| UNMATCHED=true | |
| break | |
| fi | |
| ;; | |
| esac | |
| done | |
| if [ "$UNMATCHED" = "true" ]; then | |
| echo "Unmatched changes under skills/ detected — reviewing all skills" | |
| DIRS=$(find skills -name SKILL.md -exec dirname {} \; 2>/dev/null | tr "\n" " " | xargs) | |
| fi | |
| if [ -z "$DIRS" ]; then | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| echo "No skill files changed." | |
| else | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| echo "dirs=$DIRS" >> "$GITHUB_OUTPUT" | |
| echo "Changed skills: $DIRS" | |
| fi | |
| else | |
| # Push to main: review all skills | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| echo "dirs=" >> "$GITHUB_OUTPUT" | |
| echo "Push to main — will review all skills." | |
| fi | |
| - name: Post skip summary | |
| if: steps.detect.outputs.skip == 'true' | |
| run: | | |
| echo "## Skill Review" >> "$GITHUB_STEP_SUMMARY" | |
| echo "" >> "$GITHUB_STEP_SUMMARY" | |
| echo "No skills changed — review skipped." >> "$GITHUB_STEP_SUMMARY" | |
| - name: Review skills | |
| if: steps.detect.outputs.skip != 'true' | |
| run: | | |
| THRESHOLD=50 | |
| PASS=0 | |
| FAIL=0 | |
| SUMMARY_FILE=$(mktemp) | |
| ERRORS_FILE=$(mktemp) | |
| SKILL_DIRS_INPUT="${{ steps.detect.outputs.dirs }}" | |
| SKILL_LIST_FILE=$(mktemp) | |
| trap 'rm -f "$SKILL_LIST_FILE" "$SUMMARY_FILE" "$ERRORS_FILE"' EXIT | |
| if [ -z "$SKILL_DIRS_INPUT" ]; then | |
| # Push to main or no filter: review all skills | |
| find skills -name SKILL.md -exec dirname {} \; > "$SKILL_LIST_FILE" | |
| else | |
| # PR mode: use detected skill dirs | |
| printf '%s\n' $SKILL_DIRS_INPUT > "$SKILL_LIST_FILE" | |
| fi | |
| while IFS= read -r dir; do | |
| [ -z "$dir" ] && continue | |
| SKILL_NAME=$(basename "$dir") | |
| echo "::group::Reviewing $SKILL_NAME" | |
| EXIT_CODE=0 | |
| OUTPUT=$(tessl skill review "$dir" 2>&1) || EXIT_CODE=$? | |
| echo "$OUTPUT" | |
| echo "::endgroup::" | |
| if [ "$EXIT_CODE" -ne 0 ]; then | |
| echo "::warning::tessl skill review failed for $SKILL_NAME (exit code $EXIT_CODE)" | |
| FAIL=$((FAIL + 1)) | |
| echo "| $SKILL_NAME | error | ❌ |" >> "$SUMMARY_FILE" | |
| continue | |
| fi | |
| SCORE=$(echo "$OUTPUT" | grep -oE '[0-9]+%' | tail -1 | tr -d '%') | |
| if [ -z "$SCORE" ]; then | |
| echo "::warning::Could not parse score for $SKILL_NAME" | |
| SCORE=0 | |
| fi | |
| if [ "$SCORE" -lt "$THRESHOLD" ]; then | |
| FAIL=$((FAIL + 1)) | |
| echo " ❌ $SKILL_NAME: ${SCORE}% (below ${THRESHOLD}%)" >> "$ERRORS_FILE" | |
| echo "| $SKILL_NAME | ${SCORE}% | ❌ |" >> "$SUMMARY_FILE" | |
| else | |
| PASS=$((PASS + 1)) | |
| echo "| $SKILL_NAME | ${SCORE}% | ✅ |" >> "$SUMMARY_FILE" | |
| fi | |
| done < "$SKILL_LIST_FILE" | |
| TOTAL=$((PASS + FAIL)) | |
| # Fail if no skills were reviewed (push to main only) | |
| if [ "$TOTAL" -eq 0 ] && [ "${{ github.event_name }}" != "pull_request" ]; then | |
| echo "::error::No skills were reviewed — expected at least one" | |
| exit 1 | |
| fi | |
| # Write GitHub Step Summary | |
| { | |
| echo "## Skill Review" | |
| echo "" | |
| echo "| Skill | Score | Status |" | |
| echo "|-------|-------|--------|" | |
| cat "$SUMMARY_FILE" | |
| echo "| **Total** | **$PASS/$TOTAL passed** | $([ "$FAIL" -eq 0 ] && echo '✅' || echo '❌') |" | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| # Stdout summary for logs | |
| echo "" | |
| echo "=============================" | |
| echo " Skill Review Summary" | |
| echo "=============================" | |
| echo " Total: $TOTAL" | |
| echo " Passed: $PASS" | |
| echo " Failed: $FAIL" | |
| if [ -s "$ERRORS_FILE" ]; then | |
| echo "" | |
| echo " Failed skills:" | |
| cat "$ERRORS_FILE" | |
| fi | |
| echo "=============================" | |
| if [ "$FAIL" -gt 0 ]; then | |
| echo "::error::$FAIL skill(s) scored below ${THRESHOLD}%" | |
| exit 1 | |
| fi | |