generated from coulomb/repo-seed
Add comprehensive feedback system that enables lightweight, unstructured feedback collection from users of the issue-facade capability, establishing a continuous improvement loop grounded in real-world usage. Core Components: - .feedback/ directory structure (inbound, reviewed, archived) - Standalone CLI tool (.capability/feedback) for submission and management - Comprehensive documentation (.feedback/README.md) - Integration examples and usage guides Key Features: - Multiple submission methods (CLI, Makefile, direct file drop) - No structure imposement - accepts any text/markdown format - Automatic metadata capture (timestamp, git context, version) - Maintainer workflow (list, review, archive, create issues) - Colored terminal output for better UX - Future-ready for API endpoint evolution Integration: - Updated CAPABILITY.yaml with feedback section - Enhanced CLAUDE.md with comprehensive integration guide - Added Makefile commands (feedback, feedback-list, feedback-stats, etc.) - Created detailed usage examples (examples/feedback-example.md) Design Philosophy: - Capability-agnostic pattern (reusable across all markitect capabilities) - Decentralized (each capability owns its feedback) - Flexible (no required formats or fields) - Durable (plain markdown files, git-tracked) - Actionable (feedback lives where maintainers work) - Scalable (works for 1 user or 1000 users) Feedback Submission Examples: ./.capability/feedback submit "Your feedback" make feedback MSG="Your feedback" echo "Feedback" > .feedback/inbound/$(date +%Y%m%d)-feedback.md Maintainer Workflow: make feedback-list # List pending make feedback-stats # Show statistics make feedback-review-issue FILE=xxx # Review and create issue This establishes a robust continuous improvement loop: User Experience → Feedback → Review → Action → Improved Capability The pattern is designed to be copied to any capability in the markitect project, providing consistent feedback collection across all capabilities. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
392 lines
10 KiB
Bash
Executable File
392 lines
10 KiB
Bash
Executable File
#!/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 <filename>
|
|
#
|
|
# 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 <<EOF
|
|
---
|
|
timestamp: $timestamp
|
|
source: cli
|
|
EOF
|
|
|
|
[ -n "$category" ] && echo "category: $category"
|
|
[ -n "$contact" ] && echo "contact: $contact"
|
|
|
|
cat <<EOF
|
|
context:
|
|
git_repo: $git_repo
|
|
git_branch: $git_branch
|
|
capability_version: $cap_version
|
|
python_version: $python_version
|
|
---
|
|
|
|
EOF
|
|
}
|
|
|
|
# Submit feedback
|
|
submit_feedback() {
|
|
local content="$1"
|
|
local category="${2:-}"
|
|
local contact="${3:-}"
|
|
|
|
init_dirs
|
|
|
|
local timestamp=$(date +%Y%m%d-%H%M%S)
|
|
local hash=$(echo "$content" | md5sum 2>/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 <<EOF
|
|
feedback - Universal feedback submission tool
|
|
|
|
Usage:
|
|
feedback submit <content> [options] Submit feedback
|
|
feedback list [--reviewed|--archived] List feedback
|
|
feedback show <filename> Show specific feedback
|
|
feedback review <filename> [options] Mark feedback as reviewed (maintainers)
|
|
feedback archive <filename> Archive feedback (maintainers)
|
|
feedback stats Show feedback statistics
|
|
feedback help Show this help
|
|
|
|
Submit Options:
|
|
--category=<type> Category: bug, feature, improvement, question, other
|
|
--contact=<email> 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 <content|file> [--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 <filename>"
|
|
show_feedback "$1"
|
|
;;
|
|
review)
|
|
[ -z "${1:-}" ] && error "Usage: feedback review <filename> [--create-issue]"
|
|
review_feedback "$1" "${2:-}"
|
|
;;
|
|
archive)
|
|
[ -z "${1:-}" ] && error "Usage: feedback archive <filename>"
|
|
archive_feedback "$1"
|
|
;;
|
|
stats)
|
|
show_stats
|
|
;;
|
|
help|--help|-h)
|
|
usage
|
|
;;
|
|
*)
|
|
error "Unknown command: $command\n\n$(usage)"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
main "$@"
|