11#! /usr/bin/env bash
22#  find-skills - Find and list skills with when_to_use guidance
33#  Shows all skills by default, filters by pattern if provided
4+ #  Searches current dir + additionalDirectories from settings, then global skills
45
56set  -euo pipefail
67
78#  Determine directories
89SCRIPT_DIR=" $( cd " $( dirname " ${BASH_SOURCE[0]} " ) " &&  pwd) " 
910SKILLS_DIR=" $( cd " $SCRIPT_DIR /../.." &&  pwd) " 
1011
12+ #  Function to get additional directories from Claude Code settings
13+ #  Outputs: "path|settings_dir" for each additionalDirectory found
14+ get_additional_dirs () {
15+     local  git_root
16+     git_root=$( git rev-parse --show-toplevel 2> /dev/null) ||  return 
17+ 
18+     local  current_dir
19+     current_dir=$( pwd) 
20+ 
21+     #  Walk up from current dir to git root, checking for settings files
22+     local  check_dir=" $current_dir " 
23+     while  true ;  do 
24+         local  settings_files=(
25+             " $check_dir /.claude/settings.json" 
26+             " $check_dir /.claude/settings.local.json" 
27+         )
28+ 
29+         for  settings_file  in  " ${settings_files[@]} " ;  do 
30+             if  [[ -f  " $settings_file " ;  then 
31+                 #  Extract paths from additionalDirectories array only
32+                 #  Output format: "path|settings_dir" so we can resolve relative paths correctly
33+                 awk -v settings_dir=" $check_dir " ' 
34+                     /"additionalDirectories"/ { in_section=1; next } 
35+                     in_section && /\]/ { in_section=0; next } 
36+                     in_section && /^[[:space:]]*"/ { 
37+                         gsub(/^[[:space:]]*"|"[[:space:]]*,?[[:space:]]*$/, "") 
38+                         print $0 "|" settings_dir 
39+                     } 
40+                 '   " $settings_file " 2> /dev/null
41+             fi 
42+         done 
43+ 
44+         #  Stop if we've reached git root
45+         if  [[ " $( cd " $check_dir " &&  pwd) " ==  " $( cd " $git_root " &&  pwd) " ;  then 
46+             break 
47+         fi 
48+ 
49+         #  Move up one directory
50+         check_dir=$( dirname " $check_dir " ) 
51+ 
52+         #  Safety check
53+         if  [[ " $check_dir " ==  " /" ;  then 
54+             break 
55+         fi 
56+     done 
57+ }
58+ 
59+ #  Collect additional directories from settings (excluding current dir)
60+ CURRENT_DIR=$( pwd) 
61+ ADDITIONAL_DIRS=()
62+ 
63+ if  git rev-parse --show-toplevel > /dev/null 2>&1 ;  then 
64+     while  IFS=' |' read  -r dir settings_dir;  do 
65+         [[ -z  " $dir " &&  continue 
66+ 
67+         #  Resolve relative paths from settings_dir, absolute paths as-is
68+         if  [[ " $dir " =  /*  ]];  then 
69+             #  Absolute path - use as is
70+             dir_normalized=$( cd " $dir " 2> /dev/null &&  pwd) ||  continue 
71+         else 
72+             #  Relative path - resolve from where settings file is
73+             dir_normalized=$( cd " $settings_dir " &&  cd  " $dir " 2> /dev/null &&  pwd) ||  continue 
74+         fi 
75+ 
76+         current_normalized=$( cd " $CURRENT_DIR " 2> /dev/null &&  pwd) 
77+ 
78+         #  Skip if it's the current directory
79+         if  [[ " $dir_normalized " ==  " $current_normalized " ;  then 
80+             continue 
81+         fi 
82+ 
83+         #  Deduplicate: only add if not already in array
84+         already_added=false
85+         for  existing_dir  in  " ${ADDITIONAL_DIRS[@]} " ;  do 
86+             if  [[ " $existing_dir " ==  " $dir_normalized " ;  then 
87+                 already_added=true
88+                 break 
89+             fi 
90+         done 
91+ 
92+         if  !  $already_added ;  then 
93+             ADDITIONAL_DIRS+=(" $dir_normalized " 
94+         fi 
95+     done  <  <( get_additional_dirs) 
96+ fi 
97+ 
98+ #  Project skills: current dir .claude/skills
99+ PROJECT_SKILLS_DIR=" " 
100+ if  [[ -d  " $CURRENT_DIR /.claude/skills" ;  then 
101+     PROJECT_SKILLS_DIR=" $CURRENT_DIR /.claude/skills" 
102+ fi 
103+ 
104+ #  Additional context skills: .claude/skills in each additionalDirectory
105+ ADDITIONAL_SKILLS_DIRS=()
106+ for  dir  in  " ${ADDITIONAL_DIRS[@]} " ;  do 
107+     if  [[ -d  " $dir /.claude/skills" ;  then 
108+         ADDITIONAL_SKILLS_DIRS+=(" $dir /.claude/skills" 
109+     fi 
110+ done 
111+ 
11112SUPERPOWERS_DIR=" ${XDG_CONFIG_HOME:- $HOME / .config} /superpowers" 
12113LOG_FILE=" ${SUPERPOWERS_DIR} /search-log.jsonl" 
13114
@@ -28,11 +129,15 @@ EXAMPLES:
28129
29130OUTPUT: 
30131  Each line shows: Use skill-path/SKILL.md when [trigger] 
132+   Skills from additionalDirectories show location: Use skill-path (dirname) when [trigger] 
31133  Paths include /SKILL.md for direct use with Read tool 
134+   Current dir project skills shadow additional directory skills when paths match 
32135
33136SEARCH: 
34137  Searches both skill content AND path names. 
35-   Skills location: ~/.config/superpowers/skills/ 
138+   Current dir: .claude/skills/ in current working directory 
139+   Additional context: .claude/skills/ in each additionalDirectories from .claude/settings*.json 
140+   Global skills: ~/.config/superpowers/skills/ 
36141EOF 
37142    exit  0
38143fi 
@@ -54,7 +159,8 @@ get_skill_path() {
54159    echo  " $rel_path " 
55160}
56161
57- #  Collect all matching skills
162+ #  Collect all matching skills (track seen skills to handle shadowing)
163+ seen_skills_list=" " 
58164results=()
59165
60166#  If pattern provided, log the search
@@ -63,23 +169,86 @@ if [[ -n "$PATTERN" ]]; then
63169    echo  " {\" timestamp\" :\" $timestamp \" ,\" query\" :\" $PATTERN \" }" >>  " $LOG_FILE " 2> /dev/null ||  true 
64170fi 
65171
66- #  Search skills directory
172+ #  Search current directory project skills (no location tag)
173+ if  [[ -n  " $PROJECT_SKILLS_DIR " ;  then 
174+     while  IFS= read  -r file;  do 
175+         [[ -z  " $file " &&  continue 
176+ 
177+         #  Get path relative to .claude/skills/, then prepend "skills/" for consistency
178+         rel_path=" ${file# $PROJECT_SKILLS_DIR / } " 
179+         skill_path=" skills/${rel_path} " 
180+ 
181+         when_to_use=$( get_when_to_use " $file " ) 
182+         seen_skills_list=" ${seen_skills_list}${skill_path} " $' \n ' 
183+         results+=(" $skill_path ||$when_to_use " #  Empty location tag
184+     done  <  <( 
185+         if  [[ -n  " $PATTERN " ;  then  
186+             #  Pattern mode: search content and paths 
187+             { 
188+                 grep -E -r " $PATTERN " " $PROJECT_SKILLS_DIR /" " SKILL.md" 2> /dev/null ||  true  
189+                 find " $PROJECT_SKILLS_DIR /" " SKILL.md" 2> /dev/null |  grep -E " $PATTERN " 2> /dev/null ||  true  
190+             } |  sort -u 
191+         else  
192+             #  Show all 
193+             find " $PROJECT_SKILLS_DIR /" " SKILL.md" 2> /dev/null ||  true  
194+         fi  
195+     )  
196+ fi 
197+ 
198+ #  Search additional context directories (with location tags)
199+ for  ADDITIONAL_SKILLS_DIR  in  " ${ADDITIONAL_SKILLS_DIRS[@]} " ;  do 
200+     #  Get project directory name for location tag (parent of .claude/skills)
201+     #  /path/to/project/.claude/skills -> /path/to/project
202+     project_dir=$( dirname " $( dirname " $ADDITIONAL_SKILLS_DIR " ) " ) 
203+     location_tag=$( basename " $project_dir " ) 
204+ 
205+     while  IFS= read  -r file;  do 
206+         [[ -z  " $file " &&  continue 
207+ 
208+         rel_path=" ${file# $ADDITIONAL_SKILLS_DIR / } " 
209+         skill_path=" skills/${rel_path} " 
210+ 
211+         #  Skip if already seen (shadowed by current dir)
212+         if  echo  " $seen_skills_list " |  grep -q " ^${skill_path} $" ;  then 
213+             continue 
214+         fi 
215+ 
216+         when_to_use=$( get_when_to_use " $file " ) 
217+         seen_skills_list=" ${seen_skills_list}${skill_path} " $' \n ' 
218+         results+=(" $skill_path |$location_tag |$when_to_use " 
219+     done  <  <( 
220+         if  [[ -n  " $PATTERN " ;  then  
221+             { 
222+                 grep -E -r " $PATTERN " " $ADDITIONAL_SKILLS_DIR /" " SKILL.md" 2> /dev/null ||  true  
223+                 find " $ADDITIONAL_SKILLS_DIR /" " SKILL.md" 2> /dev/null |  grep -E " $PATTERN " 2> /dev/null ||  true  
224+             } |  sort -u 
225+         else  
226+             find " $ADDITIONAL_SKILLS_DIR /" " SKILL.md" 2> /dev/null ||  true  
227+         fi  
228+     )  
229+ done 
230+ 
231+ #  Search global skills directory (skip if shadowed by project skills)
67232while  IFS= read  -r file;  do 
68233    [[ -z  " $file " &&  continue 
69234
70235    skill_path=$( get_skill_path " $file " " $SKILLS_DIR " ) 
236+ 
237+     #  Skip if shadowed by project skill
238+     echo  " $seen_skills_list " |  grep -q " ^${skill_path} $" &&  continue 
239+ 
71240    when_to_use=$( get_when_to_use " $file " ) 
72-     results+=(" $skill_path |$when_to_use " 
241+     results+=(" $skill_path || $when_to_use "    #  Empty location tag 
73242done  <  <( 
74243    if  [[ -n  " $PATTERN " ;  then  
75-         #  Pattern mode: search content and paths 
244+         #  Pattern mode: search content and paths (exclude .claude directories)  
76245        { 
77-             grep -E -r -- " $PATTERN " " $SKILLS_DIR /" " SKILL.md" 2> /dev/null ||  true  
78-             find " $SKILLS_DIR /" " SKILL.md" 2> /dev/null |  grep -E -- " $PATTERN " 2> /dev/null ||  true  
246+             grep -E -r -- " $PATTERN " " $SKILLS_DIR /" " SKILL.md" -exclude-dir= " .claude "  - l 2> /dev/null ||  true  
247+             find " $SKILLS_DIR /" " SKILL.md" -not -path  " */.claude/* "   2> /dev/null |  grep -E -- " $PATTERN " 2> /dev/null ||  true  
79248        } |  sort -u 
80249    else  
81-         #  Show all 
82-         find " $SKILLS_DIR /" " SKILL.md" 2> /dev/null ||  true  
250+         #  Show all (exclude .claude directories)  
251+         find " $SKILLS_DIR /" " SKILL.md" -not -path  " */.claude/* "   2> /dev/null ||  true  
83252    fi  
84253) 
85254
@@ -96,11 +265,21 @@ if [[ ${#results[@]} -eq 0 ]]; then
96265fi 
97266
98267#  Sort and display results
99- printf  " %s\n" " ${results[@]} " |  sort |  while  IFS=' |' read  -r skill_path when_to_use;  do 
100-     if  [[ -n  " $when_to_use " ;  then 
101-         echo  " Use $skill_path  $when_to_use " 
268+ printf  " %s\n" " ${results[@]} " |  sort |  while  IFS=' |' read  -r skill_path location_tag when_to_use;  do 
269+     if  [[ -n  " $location_tag " ;  then 
270+         #  Additional directory skill - show with location tag
271+         if  [[ -n  " $when_to_use " ;  then 
272+             echo  " Use $skill_path  ($location_tag ) $when_to_use " 
273+         else 
274+             echo  " $skill_path  ($location_tag )" 
275+         fi 
102276    else 
103-         echo  " $skill_path " 
277+         #  Current dir or global skill - no location tag
278+         if  [[ -n  " $when_to_use " ;  then 
279+             echo  " Use $skill_path  $when_to_use " 
280+         else 
281+             echo  " $skill_path " 
282+         fi 
104283    fi 
105284done 
106285
0 commit comments