208 lines
6.4 KiB
Bash
Executable File
208 lines
6.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Fallback skill reference generator when Python is unavailable
|
|
# Requires bash 3.2+ (uses [[ ]], ${BASH_SOURCE}, ${var:0:n})
|
|
# Tools used: sed, awk, grep (standard on macOS/Linux/Git Bash)
|
|
#
|
|
# This script provides a degraded but functional skills quick reference
|
|
# when Python or PyYAML are not available on the system.
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
|
|
PLUGIN_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
SKILLS_DIR="${PLUGIN_ROOT}/skills"
|
|
|
|
# Parse a single field from YAML frontmatter
|
|
# Uses the proven sed pattern from ralph-wiggum/hooks/stop-hook.sh
|
|
extract_field() {
|
|
local frontmatter="$1"
|
|
local field="$2"
|
|
|
|
# For simple fields: fieldname: value
|
|
# For block scalars: fieldname: | followed by indented lines
|
|
echo "$frontmatter" | awk -v field="$field" '
|
|
BEGIN { found = 0; value = "" }
|
|
|
|
# Match the field we want
|
|
$0 ~ "^" field ":" {
|
|
found = 1
|
|
# Check for inline value (not block scalar)
|
|
sub("^" field ":[[:space:]]*\\|?[[:space:]]*", "")
|
|
if (length($0) > 0 && $0 !~ /^\|[[:space:]]*$/) {
|
|
value = $0
|
|
exit
|
|
}
|
|
next
|
|
}
|
|
|
|
# If we found our field and this line is indented, capture it
|
|
found && /^[[:space:]]+[^[:space:]]/ {
|
|
gsub(/^[[:space:]]+/, "")
|
|
gsub(/[[:space:]]+$/, "")
|
|
# Skip list markers for cleaner output
|
|
gsub(/^-[[:space:]]+/, "")
|
|
if (length($0) > 0 && value == "") {
|
|
value = $0
|
|
exit
|
|
}
|
|
}
|
|
|
|
# If we hit another field definition, stop
|
|
found && /^[a-z_]+:/ && $0 !~ "^" field ":" {
|
|
exit
|
|
}
|
|
|
|
END { print value }
|
|
'
|
|
}
|
|
|
|
# Parse YAML frontmatter from SKILL.md
|
|
parse_skill() {
|
|
local skill_file="$1"
|
|
local skill_dir
|
|
skill_dir=$(basename "$(dirname "$skill_file")")
|
|
|
|
# Skip shared-patterns directory
|
|
if [[ "$skill_dir" == "shared-patterns" ]]; then
|
|
return
|
|
fi
|
|
|
|
# Extract frontmatter between --- delimiters
|
|
# Pattern proven portable in ralph-wiggum/hooks/stop-hook.sh
|
|
local frontmatter
|
|
frontmatter=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$skill_file" 2>/dev/null) || return
|
|
|
|
if [[ -z "$frontmatter" ]]; then
|
|
echo "Warning: No frontmatter in $skill_file" >&2
|
|
return
|
|
fi
|
|
|
|
# Extract fields
|
|
local name description trigger
|
|
name=$(extract_field "$frontmatter" "name")
|
|
description=$(extract_field "$frontmatter" "description")
|
|
trigger=$(extract_field "$frontmatter" "trigger")
|
|
|
|
# Fallback: use when_to_use if trigger not set (backward compat)
|
|
if [[ -z "$trigger" ]]; then
|
|
trigger=$(extract_field "$frontmatter" "when_to_use")
|
|
fi
|
|
|
|
# Use directory name if name field missing
|
|
if [[ -z "$name" ]]; then
|
|
name="$skill_dir"
|
|
fi
|
|
|
|
# Default description if missing
|
|
if [[ -z "$description" ]]; then
|
|
description="(no description)"
|
|
fi
|
|
|
|
# Truncate long descriptions for quick reference
|
|
if [[ ${#description} -gt 100 ]]; then
|
|
description="${description:0:97}..."
|
|
fi
|
|
|
|
# Output as TSV for reliable parsing (dir, name, description, trigger)
|
|
printf '%s\t%s\t%s\t%s\n' "$skill_dir" "$name" "$description" "$trigger"
|
|
}
|
|
|
|
# Categorize skill based on directory name
|
|
categorize_skill() {
|
|
local dir="$1"
|
|
case "$dir" in
|
|
pre-dev-*) echo "Pre-Dev Workflow" ;;
|
|
test-*|*-debugging|condition-*|defense-*|root-cause*) echo "Testing & Debugging" ;;
|
|
*-review|dispatching-*|sharing-*) echo "Collaboration" ;;
|
|
brainstorming|writing-plans|executing-plans|*-worktrees|subagent-driven*) echo "Planning & Execution" ;;
|
|
using-*|writing-skills|testing-skills*|testing-agents*) echo "Meta Skills" ;;
|
|
*) echo "Other" ;;
|
|
esac
|
|
}
|
|
|
|
# Generate markdown output
|
|
generate_markdown() {
|
|
echo "# Ring Skills Quick Reference"
|
|
echo ""
|
|
echo "> **Note:** Python unavailable. Using bash fallback parser."
|
|
echo "> Install Python + PyYAML for full output with categories."
|
|
echo ""
|
|
|
|
local skill_count=0
|
|
local current_category=""
|
|
|
|
# Sort by category, then by name
|
|
while IFS=$'\t' read -r dir name desc trigger; do
|
|
local category
|
|
category=$(categorize_skill "$dir")
|
|
|
|
# Print category header if changed
|
|
if [[ "$category" != "$current_category" ]]; then
|
|
if [[ -n "$current_category" ]]; then
|
|
echo ""
|
|
fi
|
|
echo "## $category"
|
|
echo ""
|
|
current_category="$category"
|
|
fi
|
|
|
|
# Combine description with trigger hint if available
|
|
local display_desc="$desc"
|
|
if [[ -n "$trigger" && "$trigger" != "$desc" ]]; then
|
|
display_desc="$trigger"
|
|
fi
|
|
|
|
echo "- **${name}**: ${display_desc}"
|
|
skill_count=$((skill_count + 1))
|
|
done
|
|
|
|
echo ""
|
|
echo "## Usage"
|
|
echo ""
|
|
echo "To use a skill: Use the Skill tool with skill name"
|
|
echo "Example: \`ring-default:brainstorming\`"
|
|
|
|
# Output stats to stderr (like Python version)
|
|
echo "" >&2
|
|
echo "Generated reference for ${skill_count} skills (bash fallback)" >&2
|
|
}
|
|
|
|
# Main execution
|
|
main() {
|
|
if [[ ! -d "$SKILLS_DIR" ]]; then
|
|
echo "Error: Skills directory not found: $SKILLS_DIR" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Collect all skills with categories, then sort and generate markdown
|
|
local tmpfile
|
|
tmpfile=$(mktemp)
|
|
chmod 600 "$tmpfile" # Restrict permissions for security
|
|
trap "rm -f '$tmpfile'" EXIT INT TERM HUP
|
|
|
|
for skill_dir in "$SKILLS_DIR"/*/; do
|
|
# Skip if not a directory
|
|
[[ -d "$skill_dir" ]] || continue
|
|
|
|
local skill_file="${skill_dir}SKILL.md"
|
|
if [[ -f "$skill_file" ]]; then
|
|
local skill_line
|
|
skill_line=$(parse_skill "$skill_file")
|
|
if [[ -n "$skill_line" ]]; then
|
|
# Add category as first field for sorting
|
|
local dir name desc trigger cat
|
|
IFS=$'\t' read -r dir name desc trigger <<< "$skill_line"
|
|
cat=$(categorize_skill "$dir")
|
|
printf '%s\t%s\t%s\t%s\t%s\n' "$cat" "$dir" "$name" "$desc" "$trigger" >> "$tmpfile"
|
|
fi
|
|
else
|
|
echo "Warning: No SKILL.md in $(basename "$skill_dir")" >&2
|
|
fi
|
|
done
|
|
|
|
# Sort by category, then by name, remove category column, generate markdown
|
|
sort -t$'\t' -k1,1 -k3,3 "$tmpfile" | cut -f2- | generate_markdown
|
|
}
|
|
|
|
main "$@"
|