generated from coulomb/repo-seed
feat: implement reusable feedback capability for continuous improvement
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>
This commit is contained in:
391
.capability/feedback
Executable file
391
.capability/feedback
Executable file
@@ -0,0 +1,391 @@
|
||||
#!/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 "$@"
|
||||
Reference in New Issue
Block a user