#!/usr/bin/env bash # feedback - Universal feedback submission tool for capabilities # # Usage: # feedback submit "Your feedback text" # feedback submit path/to/feedback.md # feedback submit "Text" --category=bug --contact=me@email.com # feedback list [--reviewed] [--archived] # feedback show # # This tool can be copied to any capability that wants to use the feedback pattern. set -e FEEDBACK_DIR="feedback" INBOUND_DIR="${FEEDBACK_DIR}/inbound" REVIEWED_DIR="${FEEDBACK_DIR}/reviewed" ARCHIVED_DIR="${FEEDBACK_DIR}/archived" # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Helper functions error() { echo -e "${RED}Error: $1${NC}" >&2 exit 1 } success() { echo -e "${GREEN}$1${NC}" } info() { echo -e "${BLUE}$1${NC}" } warn() { echo -e "${YELLOW}$1${NC}" } # Check if we're in a capability directory check_capability_dir() { if [ ! -d "$FEEDBACK_DIR" ]; then error "Not in a capability directory with feedback support.\n Looking for: $FEEDBACK_DIR/\n Run from capability root or initialize with: mkdir -p $INBOUND_DIR" fi } # Initialize feedback directories init_dirs() { mkdir -p "$INBOUND_DIR" "$REVIEWED_DIR" "$ARCHIVED_DIR" } # Generate metadata generate_metadata() { local category="${1:-}" local contact="${2:-}" local timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ) local git_repo=$(git rev-parse --show-toplevel 2>/dev/null || echo "unknown") local git_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown") local python_version=$(python3 --version 2>/dev/null | cut -d' ' -f2 || echo "unknown") # Try to find capability version local cap_version="unknown" if [ -f "CAPABILITY.yaml" ]; then cap_version=$(grep "^version:" CAPABILITY.yaml | awk '{print $2}' | tr -d '"' || echo "unknown") fi cat </dev/null | cut -c1-8 || echo "$(date +%N)") local filename="${INBOUND_DIR}/${timestamp}-${hash}.md" # Check if content is a file if [ -f "$content" ]; then info "Submitting feedback from file: $content" { generate_metadata "$category" "$contact" cat "$content" } > "$filename" else info "Submitting text feedback" { generate_metadata "$category" "$contact" echo "$content" } > "$filename" fi success "✓ Feedback submitted: $filename" echo "" info "Your feedback has been recorded and will be reviewed by the capability maintainers." echo "" echo "To view: feedback show $(basename "$filename")" } # List feedback list_feedback() { local dir="$INBOUND_DIR" local label="Inbound" case "${1:-}" in --reviewed) dir="$REVIEWED_DIR" label="Reviewed" ;; --archived) dir="$ARCHIVED_DIR" label="Archived" ;; esac check_capability_dir if [ ! -d "$dir" ]; then warn "No feedback directory: $dir" return fi local count=$(ls -1 "$dir" 2>/dev/null | wc -l) echo -e "${BLUE}=== $label Feedback ($count) ===${NC}" echo "" if [ "$count" -eq 0 ]; then echo " (none)" return fi ls -lt "$dir" | tail -n +2 | while read -r line; do local file=$(echo "$line" | awk '{print $NF}') local date=$(echo "$line" | awk '{print $6, $7, $8}') # Try to extract category from metadata local category="" if [ -f "$dir/$file" ]; then category=$(grep "^category:" "$dir/$file" | awk '{print $2}' || echo "") fi # Colorize based on category local color=$NC case "$category" in bug) color=$RED ;; feature) color=$GREEN ;; improvement) color=$YELLOW ;; esac echo -e " ${color}${file}${NC}" [ -n "$category" ] && echo " Category: $category" echo " Date: $date" echo "" done } # Show specific feedback show_feedback() { local filename="$1" check_capability_dir # Search in all directories local filepath="" for dir in "$INBOUND_DIR" "$REVIEWED_DIR" "$ARCHIVED_DIR"; do if [ -f "$dir/$filename" ]; then filepath="$dir/$filename" break fi done if [ -z "$filepath" ]; then error "Feedback not found: $filename" fi echo -e "${BLUE}=== Feedback: $filename ===${NC}" echo "" cat "$filepath" } # Review feedback (move to reviewed) review_feedback() { local filename="$1" local create_issue="${2:-}" check_capability_dir init_dirs if [ ! -f "$INBOUND_DIR/$filename" ]; then error "Feedback not found in inbound: $filename" fi if [ "$create_issue" = "--create-issue" ]; then info "Creating issue from feedback..." # Extract title and body local title=$(head -20 "$INBOUND_DIR/$filename" | grep -v "^---" | grep -v "^$" | head -1 | sed 's/^# *//') local body=$(cat "$INBOUND_DIR/$filename") if command -v issue &> /dev/null; then issue create "$title" --description "$body" --label=feedback success "✓ Issue created" else warn "Issue command not found. Please create issue manually." fi fi mv "$INBOUND_DIR/$filename" "$REVIEWED_DIR/$filename" success "✓ Feedback moved to reviewed: $filename" } # Archive feedback archive_feedback() { local filename="$1" check_capability_dir init_dirs # Try both inbound and reviewed if [ -f "$INBOUND_DIR/$filename" ]; then mv "$INBOUND_DIR/$filename" "$ARCHIVED_DIR/$filename" elif [ -f "$REVIEWED_DIR/$filename" ]; then mv "$REVIEWED_DIR/$filename" "$ARCHIVED_DIR/$filename" else error "Feedback not found: $filename" fi success "✓ Feedback archived: $filename" } # Show usage usage() { cat < [options] Submit feedback feedback list [--reviewed|--archived] List feedback feedback show Show specific feedback feedback review [options] Mark feedback as reviewed (maintainers) feedback archive Archive feedback (maintainers) feedback stats Show feedback statistics feedback help Show this help Submit Options: --category= Category: bug, feature, improvement, question, other --contact= Optional contact for follow-up Review Options: --create-issue Create an issue from the feedback Examples: # Quick text feedback feedback submit "The sync command is slow with 1000+ issues" # Feedback from file feedback submit my-feedback.md # With metadata feedback submit "Bug: crashes on startup" --category=bug --contact=me@email.com # List feedback feedback list feedback list --reviewed # Show specific feedback feedback show 20251217-103045-abc12345.md # Review and create issue (maintainers) feedback review 20251217-103045-abc12345.md --create-issue # Archive (maintainers) feedback archive 20251217-103045-abc12345.md For more information, see feedback/README.md EOF } # Show statistics show_stats() { check_capability_dir local inbound=$(ls -1 "$INBOUND_DIR" 2>/dev/null | wc -l) local reviewed=$(ls -1 "$REVIEWED_DIR" 2>/dev/null | wc -l) local archived=$(ls -1 "$ARCHIVED_DIR" 2>/dev/null | wc -l) local total=$((inbound + reviewed + archived)) echo -e "${BLUE}=== Feedback Statistics ===${NC}" echo "" echo " Pending: $inbound" echo " Reviewed: $reviewed" echo " Archived: $archived" echo " ─────────────" echo " Total: $total" echo "" if [ "$inbound" -gt 0 ]; then warn "⚠ $inbound feedback items awaiting review" else success "✓ No pending feedback" fi } # Main command dispatcher main() { local command="${1:-help}" shift || true case "$command" in submit) local content="${1:-}" [ -z "$content" ] && error "Usage: feedback submit [--category=TYPE] [--contact=EMAIL]" local category="" local contact="" # Parse options shift || true while [ $# -gt 0 ]; do case "$1" in --category=*) category="${1#--category=}" ;; --contact=*) contact="${1#--contact=}" ;; *) warn "Unknown option: $1" ;; esac shift done submit_feedback "$content" "$category" "$contact" ;; list) list_feedback "$@" ;; show) [ -z "${1:-}" ] && error "Usage: feedback show " show_feedback "$1" ;; review) [ -z "${1:-}" ] && error "Usage: feedback review [--create-issue]" review_feedback "$1" "${2:-}" ;; archive) [ -z "${1:-}" ] && error "Usage: feedback archive " archive_feedback "$1" ;; stats) show_stats ;; help|--help|-h) usage ;; *) error "Unknown command: $command\n\n$(usage)" ;; esac } main "$@"