evaluate.sh 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. #!/bin/bash
  2. # evaluate.sh - Stop hook: evaluate session for skill-worthy workflows
  3. #
  4. # Suggests skill creation only when a session shows genuine workflow complexity:
  5. # - 8+ mutating tool calls (high threshold, reduces noise)
  6. # - 4+ distinct mutating tool types (diversity = workflow, not repetitive edits)
  7. # - No skill was loaded (novel work, not following existing procedure)
  8. # - Per-session cooldown file prevents re-fire on resume
  9. #
  10. # Toggle: touch ~/.claude/auto-skill.disable (global off)
  11. # touch .claude/auto-skill.disable (project off)
  12. # rm either file to re-enable
  13. #
  14. # CRITICAL: This hook must NEVER fail visibly. All errors suppressed.
  15. {
  16. INPUT=$(cat)
  17. SESSION_ID=$(printf '%s' "$INPUT" | jq -r '.session_id // empty' 2>/dev/null)
  18. [ -z "$SESSION_ID" ] && exit 0
  19. SHORT_ID="${SESSION_ID:0:8}"
  20. TRACK_FILE="/tmp/claude_autoskill_${SHORT_ID}"
  21. # No tracking file = no tool calls recorded
  22. [ -f "$TRACK_FILE" ] || exit 0
  23. # --- Toggle: global or project disable ---
  24. if [ -f "$HOME/.claude/auto-skill.disable" ] || [ -f ".claude/auto-skill.disable" ]; then
  25. rm -f "$TRACK_FILE"
  26. exit 0
  27. fi
  28. # --- Per-session cooldown: only suggest once per session ---
  29. SUGGESTED_FILE="/tmp/claude_autoskill_suggested_${SHORT_ID}"
  30. if [ -f "$SUGGESTED_FILE" ]; then
  31. rm -f "$TRACK_FILE"
  32. exit 0
  33. fi
  34. # --- Classify tools ---
  35. READ_ONLY_LIST=" Read Glob Grep LS NotebookRead TaskList TaskGet TaskCreate TaskUpdate TaskOutput TaskStop "
  36. SKILL_LOADED=false
  37. TOTAL=0
  38. WRITES=0
  39. UNIQUE_TYPES=""
  40. while IFS= read -r tool; do
  41. [ -z "$tool" ] && continue
  42. TOTAL=$((TOTAL + 1))
  43. # Check if a skill was loaded
  44. [ "$tool" = "Skill" ] && SKILL_LOADED=true
  45. # Check if read-only (space-padded list for exact word match)
  46. case "$READ_ONLY_LIST" in
  47. *" ${tool} "*) continue ;;
  48. esac
  49. # Skip Skill tool itself
  50. [ "$tool" = "Skill" ] && continue
  51. WRITES=$((WRITES + 1))
  52. # Track unique mutating tool types
  53. case " $UNIQUE_TYPES " in
  54. *" ${tool} "*) ;; # already seen
  55. *) UNIQUE_TYPES="${UNIQUE_TYPES} ${tool}" ;;
  56. esac
  57. done < "$TRACK_FILE"
  58. # Count unique types (count words in UNIQUE_TYPES)
  59. UNIQUE_COUNT=0
  60. for _ in $UNIQUE_TYPES; do
  61. UNIQUE_COUNT=$((UNIQUE_COUNT + 1))
  62. done
  63. # Build tool summary before cleanup
  64. TOOL_SUMMARY=$(sort "$TRACK_FILE" | uniq -c | sort -rn | head -6 | awk '{printf "%s(%d) ", $2, $1}')
  65. # Clean up tracking file
  66. rm -f "$TRACK_FILE"
  67. # --- Gate 1: Skill was loaded = not novel work ---
  68. [ "$SKILL_LOADED" = true ] && exit 0
  69. # --- Gate 2: Minimum 8 mutating operations ---
  70. [ "$WRITES" -lt 8 ] && exit 0
  71. # --- Gate 3: Minimum 4 distinct mutating tool types ---
  72. [ "$UNIQUE_COUNT" -lt 4 ] && exit 0
  73. # --- All gates passed: suggest ---
  74. # Mark this session as suggested (prevents repeat on resume)
  75. touch "$SUGGESTED_FILE" 2>/dev/null
  76. MSG="Skill-worthy session: ${WRITES} mutating ops across ${UNIQUE_COUNT} tool types (${TOTAL} total): ${TOOL_SUMMARY}- run /auto-skill to capture this workflow."
  77. ESCAPED=$(printf '%s' "$MSG" | sed 's/"/\\"/g' | tr '\n' ' ')
  78. printf '{"systemMessage":"%s"}\n' "$ESCAPED"
  79. } 2>/dev/null
  80. exit 0