|
@@ -4,9 +4,18 @@
|
|
|
# Suggests skill creation only when a session shows genuine workflow complexity:
|
|
# Suggests skill creation only when a session shows genuine workflow complexity:
|
|
|
# - 8+ mutating tool calls (high threshold, reduces noise)
|
|
# - 8+ mutating tool calls (high threshold, reduces noise)
|
|
|
# - 4+ distinct mutating tool types (diversity = workflow, not repetitive edits)
|
|
# - 4+ distinct mutating tool types (diversity = workflow, not repetitive edits)
|
|
|
-# - No skill was loaded (novel work, not following existing procedure)
|
|
|
|
|
|
|
+# - No non-harness skill was loaded (novel work, not following a recipe).
|
|
|
|
|
+# Harness skills (sync, save, introspect, auto-skill, setperms, tool-discovery)
|
|
|
|
|
+# are whitelisted — they're meta/bootstrap, not domain-specific, so loading
|
|
|
|
|
+# them shouldn't disqualify an otherwise novel session.
|
|
|
# - Per-session cooldown file prevents re-fire on resume
|
|
# - Per-session cooldown file prevents re-fire on resume
|
|
|
#
|
|
#
|
|
|
|
|
+# Output channels (when a suggestion fires):
|
|
|
|
|
+# 1. systemMessage JSON on stdout - visible to Claude on next turn
|
|
|
|
|
+# 2. Appended line to ~/.claude/auto-skill/pending.log - visible to user
|
|
|
|
|
+# at next /sync (since Claude's systemMessage often dies silently if
|
|
|
|
|
+# the user's next prompt doesn't invite it to be mentioned).
|
|
|
|
|
+#
|
|
|
# Toggle: touch ~/.claude/auto-skill.disable (global off)
|
|
# Toggle: touch ~/.claude/auto-skill.disable (global off)
|
|
|
# touch .claude/auto-skill.disable (project off)
|
|
# touch .claude/auto-skill.disable (project off)
|
|
|
# rm either file to re-enable
|
|
# rm either file to re-enable
|
|
@@ -40,6 +49,8 @@
|
|
|
|
|
|
|
|
# --- Classify tools ---
|
|
# --- Classify tools ---
|
|
|
READ_ONLY_LIST=" Read Glob Grep LS NotebookRead TaskList TaskGet TaskCreate TaskUpdate TaskOutput TaskStop "
|
|
READ_ONLY_LIST=" Read Glob Grep LS NotebookRead TaskList TaskGet TaskCreate TaskUpdate TaskOutput TaskStop "
|
|
|
|
|
+ # Harness skills: loading these should NOT disqualify a session
|
|
|
|
|
+ HARNESS_SKILLS=" sync save introspect auto-skill setperms tool-discovery "
|
|
|
SKILL_LOADED=false
|
|
SKILL_LOADED=false
|
|
|
TOTAL=0
|
|
TOTAL=0
|
|
|
WRITES=0
|
|
WRITES=0
|
|
@@ -49,17 +60,29 @@
|
|
|
[ -z "$tool" ] && continue
|
|
[ -z "$tool" ] && continue
|
|
|
TOTAL=$((TOTAL + 1))
|
|
TOTAL=$((TOTAL + 1))
|
|
|
|
|
|
|
|
- # Check if a skill was loaded
|
|
|
|
|
- [ "$tool" = "Skill" ] && SKILL_LOADED=true
|
|
|
|
|
|
|
+ # Handle Skill tool (tagged as "Skill:<name>" by track-tools.sh, or bare
|
|
|
|
|
+ # "Skill" from pre-whitelist versions)
|
|
|
|
|
+ case "$tool" in
|
|
|
|
|
+ Skill:*)
|
|
|
|
|
+ skill_name="${tool#Skill:}"
|
|
|
|
|
+ # Is it a harness skill? If so, ignore entirely.
|
|
|
|
|
+ case "$HARNESS_SKILLS" in
|
|
|
|
|
+ *" ${skill_name} "*) continue ;;
|
|
|
|
|
+ *) SKILL_LOADED=true; continue ;;
|
|
|
|
|
+ esac
|
|
|
|
|
+ ;;
|
|
|
|
|
+ Skill)
|
|
|
|
|
+ # Legacy format (pre-whitelist): conservatively disqualify
|
|
|
|
|
+ SKILL_LOADED=true
|
|
|
|
|
+ continue
|
|
|
|
|
+ ;;
|
|
|
|
|
+ esac
|
|
|
|
|
|
|
|
# Check if read-only (space-padded list for exact word match)
|
|
# Check if read-only (space-padded list for exact word match)
|
|
|
case "$READ_ONLY_LIST" in
|
|
case "$READ_ONLY_LIST" in
|
|
|
*" ${tool} "*) continue ;;
|
|
*" ${tool} "*) continue ;;
|
|
|
esac
|
|
esac
|
|
|
|
|
|
|
|
- # Skip Skill tool itself
|
|
|
|
|
- [ "$tool" = "Skill" ] && continue
|
|
|
|
|
-
|
|
|
|
|
WRITES=$((WRITES + 1))
|
|
WRITES=$((WRITES + 1))
|
|
|
|
|
|
|
|
# Track unique mutating tool types
|
|
# Track unique mutating tool types
|
|
@@ -81,7 +104,7 @@
|
|
|
# Clean up tracking file
|
|
# Clean up tracking file
|
|
|
rm -f "$TRACK_FILE"
|
|
rm -f "$TRACK_FILE"
|
|
|
|
|
|
|
|
- # --- Gate 1: Skill was loaded = not novel work ---
|
|
|
|
|
|
|
+ # --- Gate 1: Non-harness skill was loaded = following a recipe, not novel ---
|
|
|
[ "$SKILL_LOADED" = true ] && exit 0
|
|
[ "$SKILL_LOADED" = true ] && exit 0
|
|
|
|
|
|
|
|
# --- Gate 2: Minimum 8 mutating operations ---
|
|
# --- Gate 2: Minimum 8 mutating operations ---
|
|
@@ -95,6 +118,19 @@
|
|
|
# Mark this session as suggested (prevents repeat on resume)
|
|
# Mark this session as suggested (prevents repeat on resume)
|
|
|
touch "$SUGGESTED_FILE" 2>/dev/null
|
|
touch "$SUGGESTED_FILE" 2>/dev/null
|
|
|
|
|
|
|
|
|
|
+ # Append to persistent log so the human can see suggestions at next /sync.
|
|
|
|
|
+ # systemMessage goes to Claude; this log goes to the user.
|
|
|
|
|
+ # Format: ISO8601 | session_id | cwd | writes | unique | total | summary
|
|
|
|
|
+ LOG_DIR="$HOME/.claude/auto-skill"
|
|
|
|
|
+ LOG_FILE="$LOG_DIR/pending.log"
|
|
|
|
|
+ mkdir -p "$LOG_DIR" 2>/dev/null
|
|
|
|
|
+ TS=$(date -Iseconds 2>/dev/null || date '+%Y-%m-%dT%H:%M:%S%z')
|
|
|
|
|
+ CWD=$(pwd 2>/dev/null || echo "unknown")
|
|
|
|
|
+ CLEAN_SUMMARY=$(printf '%s' "$TOOL_SUMMARY" | tr '|' '/' | tr -s ' ')
|
|
|
|
|
+ printf '%s|%s|%s|%d|%d|%d|%s\n' \
|
|
|
|
|
+ "$TS" "$SHORT_ID" "$CWD" "$WRITES" "$UNIQUE_COUNT" "$TOTAL" "$CLEAN_SUMMARY" \
|
|
|
|
|
+ >> "$LOG_FILE" 2>/dev/null
|
|
|
|
|
+
|
|
|
MSG="Skill-worthy session: ${WRITES} mutating ops across ${UNIQUE_COUNT} tool types (${TOTAL} total): ${TOOL_SUMMARY}- run /auto-skill to capture this workflow."
|
|
MSG="Skill-worthy session: ${WRITES} mutating ops across ${UNIQUE_COUNT} tool types (${TOTAL} total): ${TOOL_SUMMARY}- run /auto-skill to capture this workflow."
|
|
|
|
|
|
|
|
ESCAPED=$(printf '%s' "$MSG" | sed 's/"/\\"/g' | tr '\n' ' ')
|
|
ESCAPED=$(printf '%s' "$MSG" | sed 's/"/\\"/g' | tr '\n' ' ')
|