Jelajahi Sumber

Add unified scripts system with find-skills and run

Consolidates skill discovery and adds generic runner for cross-platform compatibility.

Changes:
- Created scripts/find-skills: Unified tool (show all + filter by pattern)
  - Shows descriptions by default
  - Searches personal first, then core (shadowing)
  - Logs searches for gap analysis
  - Bash 3.2 compatible

- Created scripts/run: Generic runner for any skill script
  - Searches personal superpowers first, then core
  - Enables running arbitrary skill scripts without CLAUDE_PLUGIN_ROOT env var
  - Example: scripts/run skills/collaboration/remembering-conversations/tool/search-conversations

- Fixed bash 3.2 compatibility in list-skills, skills-search
  - Replaced associative arrays with newline-delimited lists
  - Works on macOS default bash (3.2) and Linux bash 4+

- Updated all documentation to reference scripts/find-skills
- Removed redundant wrapper scripts

This solves the CLAUDE_PLUGIN_ROOT environment variable issue - scripts
can now be called from anywhere without needing the env var set.
Jesse Vincent 11 bulan lalu
induk
melakukan
16b764689a

+ 18 - 7
README.md

@@ -62,12 +62,12 @@ See `skills/meta/sharing-skills` for how to contribute to core.
 
 ### Finding Skills
 
-Search both personal and core skills before starting any task:
+Find both personal and core skills before starting any task:
 
 ```bash
-${CLAUDE_PLUGIN_ROOT}/skills/getting-started/list-skills  # See all available
-${CLAUDE_PLUGIN_ROOT}/skills/getting-started/skills-search 'test.*driven|TDD'
-${CLAUDE_PLUGIN_ROOT}/skills/getting-started/skills-search 'debug.*systematic'
+${CLAUDE_PLUGIN_ROOT}/scripts/find-skills              # All skills with descriptions
+${CLAUDE_PLUGIN_ROOT}/scripts/find-skills test         # Filter by pattern
+${CLAUDE_PLUGIN_ROOT}/scripts/find-skills 'TDD|debug'  # Regex pattern
 ```
 
 ### Using Slash Commands
@@ -127,14 +127,25 @@ ${CLAUDE_PLUGIN_ROOT}/skills/getting-started/skills-search 'debug.*systematic'
 
 ### Tools
 
-- **skills-search** - Grep-powered skill discovery
-- **search-conversations** - Semantic search of past Claude sessions
+**In `scripts/` directory:**
+- **find-skills** - Unified skill discovery with descriptions (replaces list-skills + skills-search)
+- **run** - Generic runner for any skill script (searches personal then core)
+
+**Skill-specific tools:**
+- **search-conversations** - Semantic search of past Claude sessions (in remembering-conversations skill)
+
+**Using scripts:**
+```bash
+${CLAUDE_PLUGIN_ROOT}/scripts/find-skills              # Show all skills
+${CLAUDE_PLUGIN_ROOT}/scripts/find-skills pattern      # Search skills
+${CLAUDE_PLUGIN_ROOT}/scripts/run <path> [args]        # Run any skill script
+```
 
 ## How It Works
 
 1. **SessionStart Hook** - Auto-setup personal skills repo, inject core skills context
 2. **Two-Tier Skills** - Personal skills (`~/.config/superpowers/skills/`) + Core skills (plugin)
-3. **Skills Discovery** - `list-skills` and `skills-search` find skills from both locations
+3. **Skills Discovery** - `find-skills` searches both locations with descriptions
 4. **Shadowing** - Personal skills override core skills when paths match
 5. **Mandatory Workflow** - Skills become required when they exist for your task
 6. **Gap Tracking** - Failed searches logged to `~/.config/superpowers/search-log.jsonl`

+ 142 - 0
scripts/find-skills

@@ -0,0 +1,142 @@
+#!/usr/bin/env bash
+# find-skills - Find and list skills with descriptions
+# Shows all skills by default, filters by pattern if provided
+# Searches personal superpowers first, then core (personal shadows core)
+
+set -euo pipefail
+
+# Determine directories
+PERSONAL_SUPERPOWERS_DIR="${PERSONAL_SUPERPOWERS_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/superpowers}"
+PERSONAL_SKILLS_DIR="${PERSONAL_SUPERPOWERS_DIR}/skills"
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+PLUGIN_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
+CORE_SKILLS_DIR="${PLUGIN_ROOT}/skills"
+
+LOG_FILE="${PERSONAL_SUPERPOWERS_DIR}/search-log.jsonl"
+
+# Show help
+if [[ "${1:-}" == "--help" ]] || [[ "${1:-}" == "-h" ]]; then
+    cat <<'EOF'
+find-skills - Find and list skills with descriptions
+
+USAGE:
+  find-skills              Show all skills with descriptions
+  find-skills PATTERN      Filter skills by grep pattern
+  find-skills --help       Show this help
+
+EXAMPLES:
+  find-skills                        # All skills
+  find-skills test                   # Skills matching "test"
+  find-skills 'test.*driven|TDD'     # Regex pattern
+
+OUTPUT:
+  Each line shows: skill-path - description
+  Personal skills listed first, then core skills
+  Personal skills shadow core skills when paths match
+
+SEARCH:
+  Searches both skill content AND path names.
+  Personal skills at: ~/.config/superpowers/skills/
+  Core skills at: plugin installation directory
+EOF
+    exit 0
+fi
+
+# Get pattern (optional)
+PATTERN="${1:-}"
+
+# Function to extract description from SKILL.md
+get_description() {
+    local file="$1"
+    grep "^description:" "$file" 2>/dev/null | sed 's/description: *//' || echo ""
+}
+
+# Function to get relative skill path
+get_skill_path() {
+    local file="$1"
+    local base_dir="$2"
+    local rel_path="${file#$base_dir/}"
+    echo "${rel_path%/SKILL.md}"
+}
+
+# Collect all matching skills (use simple list for bash 3.2 compatibility)
+seen_skills_list=""
+results=()
+
+# If pattern provided, log the search
+if [[ -n "$PATTERN" ]]; then
+    timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+    echo "{\"timestamp\":\"$timestamp\",\"query\":\"$PATTERN\"}" >> "$LOG_FILE" 2>/dev/null || true
+fi
+
+# Search personal skills first
+if [[ -d "$PERSONAL_SKILLS_DIR" ]]; then
+    while IFS= read -r file; do
+        [[ -z "$file" ]] && continue
+
+        skill_path=$(get_skill_path "$file" "$PERSONAL_SKILLS_DIR")
+        description=$(get_description "$file")
+
+        seen_skills_list="${seen_skills_list}${skill_path}"$'\n'
+        results+=("$skill_path|$description")
+    done < <(
+        if [[ -n "$PATTERN" ]]; then
+            # Pattern mode: search content and paths
+            {
+                grep -E -r "$PATTERN" "$PERSONAL_SKILLS_DIR/" --include="SKILL.md" -l 2>/dev/null || true
+                find "$PERSONAL_SKILLS_DIR/" -name "SKILL.md" -type f 2>/dev/null | grep -E "$PATTERN" 2>/dev/null || true
+            } | sort -u
+        else
+            # Show all
+            find "$PERSONAL_SKILLS_DIR/" -name "SKILL.md" -type f 2>/dev/null || true
+        fi
+    )
+fi
+
+# Search core skills (only if not shadowed)
+while IFS= read -r file; do
+    [[ -z "$file" ]] && continue
+
+    skill_path=$(get_skill_path "$file" "$CORE_SKILLS_DIR")
+
+    # Skip if shadowed by personal skill
+    echo "$seen_skills_list" | grep -q "^${skill_path}$" && continue
+
+    description=$(get_description "$file")
+    results+=("$skill_path|$description")
+done < <(
+    if [[ -n "$PATTERN" ]]; then
+        # Pattern mode: search content and paths
+        {
+            grep -E -r "$PATTERN" "$CORE_SKILLS_DIR/" --include="SKILL.md" -l 2>/dev/null || true
+            find "$CORE_SKILLS_DIR/" -name "SKILL.md" -type f 2>/dev/null | grep -E "$PATTERN" 2>/dev/null || true
+        } | sort -u
+    else
+        # Show all
+        find "$CORE_SKILLS_DIR/" -name "SKILL.md" -type f 2>/dev/null || true
+    fi
+)
+
+# Check if we found anything
+if [[ ${#results[@]} -eq 0 ]]; then
+    if [[ -n "$PATTERN" ]]; then
+        echo "❌ No skills found matching: $PATTERN"
+        echo ""
+        echo "Search logged. If a skill should exist, consider writing it!"
+    else
+        echo "❌ No skills found"
+    fi
+    exit 0
+fi
+
+# Sort and display results
+printf "%s\n" "${results[@]}" | sort | while IFS='|' read -r skill_path description; do
+    if [[ -n "$description" ]]; then
+        echo "skills/$skill_path - $description"
+    else
+        echo "skills/$skill_path"
+    fi
+done
+
+exit 0

+ 55 - 0
scripts/run

@@ -0,0 +1,55 @@
+#!/usr/bin/env bash
+# Generic runner for skill scripts
+# Searches personal superpowers first, then core plugin
+#
+# Usage: scripts/run <skill-relative-path> [args...]
+# Example: scripts/run skills/collaboration/remembering-conversations/tool/search-conversations "query"
+
+set -euo pipefail
+
+if [[ $# -eq 0 ]]; then
+    cat <<'EOF'
+Usage: scripts/run <skill-relative-path> [args...]
+
+Runs scripts from skills, checking personal superpowers first, then core.
+
+Examples:
+  scripts/run skills/collaboration/remembering-conversations/tool/search-conversations "query"
+  scripts/run skills/getting-started/list-skills
+  scripts/run skills/getting-started/skills-search "pattern"
+
+The script will be found at:
+  1. ~/.config/superpowers/<skill-relative-path> (personal, if exists)
+  2. ${CLAUDE_PLUGIN_ROOT}/<skill-relative-path> (core plugin)
+EOF
+    exit 1
+fi
+
+# Get the script path to run
+SCRIPT_PATH="$1"
+shift  # Remove script path from args, leaving remaining args
+
+# Determine directories
+PERSONAL_SUPERPOWERS_DIR="${PERSONAL_SUPERPOWERS_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/superpowers}"
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+PLUGIN_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
+
+# Try personal superpowers first
+PERSONAL_SCRIPT="${PERSONAL_SUPERPOWERS_DIR}/${SCRIPT_PATH}"
+if [[ -x "$PERSONAL_SCRIPT" ]]; then
+    exec "$PERSONAL_SCRIPT" "$@"
+fi
+
+# Fall back to core plugin
+CORE_SCRIPT="${PLUGIN_ROOT}/${SCRIPT_PATH}"
+if [[ -x "$CORE_SCRIPT" ]]; then
+    exec "$CORE_SCRIPT" "$@"
+fi
+
+# Not found
+echo "Error: Script not found: $SCRIPT_PATH" >&2
+echo "" >&2
+echo "Searched:" >&2
+echo "  $PERSONAL_SCRIPT (personal)" >&2
+echo "  $CORE_SCRIPT (core)" >&2
+exit 1

+ 6 - 10
skills/getting-started/SKILL.md

@@ -17,7 +17,7 @@ Personal skills shadow core skills when names match.
 
 **RIGHT NOW**: Run this to see what skills are available:
 ```bash
-${CLAUDE_PLUGIN_ROOT}/skills/getting-started/list-skills
+${CLAUDE_PLUGIN_ROOT}/scripts/find-skills
 ```
 
 **THEN**: Follow the workflows below based on what your partner is asking for.
@@ -51,17 +51,13 @@ ${CLAUDE_PLUGIN_ROOT}/skills/getting-started/list-skills
 
 ## Mandatory Workflow 2: Before ANY Task
 
-**1. List available skills** (to avoid useless searches):
+**1. Find skills** (shows all, or filter by pattern):
 ```bash
-${CLAUDE_PLUGIN_ROOT}/skills/getting-started/list-skills
+${CLAUDE_PLUGIN_ROOT}/scripts/find-skills           # Show all
+${CLAUDE_PLUGIN_ROOT}/scripts/find-skills PATTERN   # Filter by pattern
 ```
 
-**2. Search skills** (when you need something specific):
-```bash
-${CLAUDE_PLUGIN_ROOT}/skills/getting-started/skills-search PATTERN
-```
-
-**3. Search conversations:**
+**2. Search conversations:**
 Dispatch subagent (see Workflow 3) to check for relevant past work.
 
 **If skills found:**
@@ -186,7 +182,7 @@ Your human partner's specific instructions describe WHAT to do, not HOW.
 
 **Starting conversation?** You just read this. Good.
 
-**Starting any task?** Run skills-search first, announce usage, follow what you find.
+**Starting any task?** Run find-skills first, announce usage, follow what you find.
 
 **Skill has checklist?** TodoWrite for every item.
 

+ 6 - 4
skills/getting-started/list-skills

@@ -19,8 +19,8 @@ fi
 PERSONAL_SUPERPOWERS_DIR="${PERSONAL_SUPERPOWERS_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/superpowers}"
 PERSONAL_SKILLS_DIR="${PERSONAL_SUPERPOWERS_DIR}/skills"
 
-# Collect all skill paths with deduplication
-declare -A seen_skills
+# Collect all skill paths with deduplication (bash 3.2 compatible)
+seen_skills_list=""
 all_skills=()
 
 # Personal skills first (take precedence)
@@ -29,7 +29,7 @@ if [[ -d "$PERSONAL_SKILLS_DIR" ]]; then
         skill_path="${file#$PERSONAL_SKILLS_DIR/}"
         skill_path="${skill_path%/SKILL.md}"
         if [[ -n "$skill_path" ]]; then
-            seen_skills["$skill_path"]=1
+            seen_skills_list="${seen_skills_list}${skill_path}"$'\n'
             all_skills+=("$skill_path")
         fi
     done < <(find "$PERSONAL_SKILLS_DIR" -name "SKILL.md" -type f 2>/dev/null || true)
@@ -39,7 +39,9 @@ fi
 while IFS= read -r file; do
     skill_path="${file#$CORE_SKILLS_DIR/}"
     skill_path="${skill_path%/SKILL.md}"
-    if [[ -n "$skill_path" ]] && [[ -z "${seen_skills[$skill_path]:-}" ]]; then
+    if [[ -n "$skill_path" ]]; then
+        # Skip if already seen in personal skills
+        echo "$seen_skills_list" | grep -q "^${skill_path}$" && continue
         all_skills+=("$skill_path")
     fi
 done < <(find "$CORE_SKILLS_DIR" -name "SKILL.md" -type f 2>/dev/null || true)

+ 4 - 4
skills/getting-started/skills-search

@@ -69,8 +69,8 @@ path_matches_core=$(echo "$all_skills_core" | grep -E "$@" 2>/dev/null || true)
 # Combine all matches
 all_matches=$(printf "%s\n%s\n%s\n%s" "$content_matches_personal" "$content_matches_core" "$path_matches_personal" "$path_matches_core" | grep -v '^$' || true)
 
-# Deduplicate by skill path (personal shadows core)
-declare -A seen_skills
+# Deduplicate by skill path (personal shadows core) - bash 3.2 compatible
+seen_skills_list=""
 results=""
 while IFS= read -r file; do
     # Extract skill path relative to its base directory
@@ -82,8 +82,8 @@ while IFS= read -r file; do
     skill_path="${skill_path%/SKILL.md}"
 
     # Only include if we haven't seen this skill path yet
-    if [[ -z "${seen_skills[$skill_path]:-}" ]]; then
-        seen_skills["$skill_path"]=1
+    if ! echo "$seen_skills_list" | grep -q "^${skill_path}$"; then
+        seen_skills_list="${seen_skills_list}${skill_path}"$'\n'
         results="${results}${file}"$'\n'
     fi
 done <<< "$all_matches"

+ 2 - 2
skills/meta/setting-up-personal-superpowers/SKILL.md

@@ -81,7 +81,7 @@ gh repo edit --add-topic superpowers
 
 **Personal skills shadow core skills** - if you have `~/.config/superpowers/skills/testing/test-driven-development/SKILL.md`, it will be used instead of the core version.
 
-The `list-skills` and `skills-search` tools automatically search both locations with deduplication.
+The `find-skills` tool automatically searches both locations with deduplication.
 
 ## Writing Skills
 
@@ -145,7 +145,7 @@ File a bug at https://github.com/obra/superpowers/issues
 **Personal skills not being found:**
 - Check `~/.config/superpowers/skills/` exists
 - Verify skill has `SKILL.md` file
-- Run `${CLAUDE_PLUGIN_ROOT}/skills/getting-started/list-skills` to see if it appears
+- Run `${CLAUDE_PLUGIN_ROOT}/scripts/find-skills` to see if it appears
 
 **GitHub push failed:**
 - Check `gh auth status`

+ 1 - 1
skills/meta/writing-skills/SKILL.md

@@ -576,7 +576,7 @@ Deploying untested skills = deploying untested code. It's a violation of quality
 How future Claude finds your skill:
 
 1. **Encounters problem** ("tests are flaky")
-2. **Searches skills** using `skills-search` tool (checks personal then core)
+2. **Searches skills** using `find-skills` tool (checks personal then core)
 3. **Finds SKILL.md** (rich when_to_use matches)
 4. **Scans overview** (is this relevant?)
 5. **Reads patterns** (quick reference table)