| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343 |
- #!/usr/bin/env bash
- #############################################################################
- # Registry Validator Script
- # Validates that all paths in registry.json point to actual files
- # Exit codes:
- # 0 = All paths valid
- # 1 = Missing files found
- # 2 = Registry parse error or missing dependencies
- #############################################################################
- set -e
- # Colors
- RED='\033[0;31m'
- GREEN='\033[0;32m'
- YELLOW='\033[1;33m'
- BLUE='\033[0;34m'
- CYAN='\033[0;36m'
- BOLD='\033[1m'
- NC='\033[0m'
- # Configuration
- REGISTRY_FILE="registry.json"
- REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
- VERBOSE=false
- FIX_MODE=false
- # Counters
- TOTAL_PATHS=0
- VALID_PATHS=0
- MISSING_PATHS=0
- ORPHANED_FILES=0
- # Arrays to store results
- declare -a MISSING_FILES
- declare -a ORPHANED_COMPONENTS
- #############################################################################
- # Utility Functions
- #############################################################################
- print_header() {
- echo -e "${CYAN}${BOLD}"
- echo "╔════════════════════════════════════════════════════════════════╗"
- echo "║ ║"
- echo "║ Registry Validator v1.0.0 ║"
- echo "║ ║"
- echo "╚════════════════════════════════════════════════════════════════╝"
- echo -e "${NC}"
- }
- print_success() {
- echo -e "${GREEN}✓${NC} $1"
- }
- print_error() {
- echo -e "${RED}✗${NC} $1"
- }
- print_warning() {
- echo -e "${YELLOW}⚠${NC} $1"
- }
- print_info() {
- echo -e "${BLUE}ℹ${NC} $1"
- }
- usage() {
- echo "Usage: $0 [OPTIONS]"
- echo ""
- echo "Options:"
- echo " -v, --verbose Show detailed validation output"
- echo " -f, --fix Suggest fixes for missing files"
- echo " -h, --help Show this help message"
- echo ""
- echo "Exit codes:"
- echo " 0 = All paths valid"
- echo " 1 = Missing files found"
- echo " 2 = Registry parse error or missing dependencies"
- exit 0
- }
- #############################################################################
- # Dependency Checks
- #############################################################################
- check_dependencies() {
- local missing_deps=()
-
- if ! command -v jq &> /dev/null; then
- missing_deps+=("jq")
- fi
-
- if [ ${#missing_deps[@]} -ne 0 ]; then
- print_error "Missing required dependencies: ${missing_deps[*]}"
- echo ""
- echo "Please install them:"
- echo " macOS: brew install ${missing_deps[*]}"
- echo " Ubuntu: sudo apt-get install ${missing_deps[*]}"
- echo " Fedora: sudo dnf install ${missing_deps[*]}"
- exit 2
- fi
- }
- #############################################################################
- # Registry Validation
- #############################################################################
- validate_registry_file() {
- if [ ! -f "$REGISTRY_FILE" ]; then
- print_error "Registry file not found: $REGISTRY_FILE"
- exit 2
- fi
-
- if ! jq empty "$REGISTRY_FILE" 2>/dev/null; then
- print_error "Registry file is not valid JSON"
- exit 2
- fi
-
- print_success "Registry file is valid JSON"
- }
- validate_component_paths() {
- local category=$1
- local category_display=$2
-
- [ "$VERBOSE" = true ] && echo -e "\n${BOLD}Checking ${category_display}...${NC}"
-
- # Get all components in this category
- local components
- components=$(jq -r ".components.${category}[]? | @json" "$REGISTRY_FILE" 2>/dev/null)
-
- if [ -z "$components" ]; then
- [ "$VERBOSE" = true ] && print_info "No ${category_display} found in registry"
- return
- fi
-
- while IFS= read -r component; do
- local id
- local path
- local name
- id=$(echo "$component" | jq -r '.id')
- path=$(echo "$component" | jq -r '.path')
- name=$(echo "$component" | jq -r '.name')
-
- TOTAL_PATHS=$((TOTAL_PATHS + 1))
-
- # Check if file exists
- if [ -f "$REPO_ROOT/$path" ]; then
- VALID_PATHS=$((VALID_PATHS + 1))
- [ "$VERBOSE" = true ] && print_success "${category_display}: ${name} (${id})"
- else
- MISSING_PATHS=$((MISSING_PATHS + 1))
- MISSING_FILES+=("${category}:${id}|${name}|${path}")
- print_error "${category_display}: ${name} (${id}) - File not found: ${path}"
-
- # Try to find similar files if in fix mode
- if [ "$FIX_MODE" = true ]; then
- suggest_fix "$path" "$id"
- fi
- fi
- done <<< "$components"
- }
- suggest_fix() {
- local missing_path=$1
- local component_id=$2
-
- # Extract directory and filename
- local dir
- local base_dir
- dir=$(dirname "$missing_path")
- base_dir=$(echo "$dir" | cut -d'/' -f1-3) # e.g., .opencode/command
-
- # Look for similar files in the expected directory and subdirectories
- local similar_files
- similar_files=$(find "$REPO_ROOT/$base_dir" -type f -name "*.md" 2>/dev/null | grep -i "$component_id" || true)
-
- if [ -n "$similar_files" ]; then
- echo -e " ${YELLOW}→ Possible matches:${NC}"
- while IFS= read -r file; do
- local rel_path="${file#"$REPO_ROOT"/}"
- echo -e " ${CYAN}${rel_path}${NC}"
- done <<< "$similar_files"
- fi
- }
- scan_for_orphaned_files() {
- [ "$VERBOSE" = true ] && echo -e "\n${BOLD}Scanning for orphaned files...${NC}"
-
- # Get all paths from registry
- local registry_paths
- registry_paths=$(jq -r '.components | to_entries[] | .value[] | .path' "$REGISTRY_FILE" 2>/dev/null | sort -u)
-
- # Scan .opencode directory for markdown files
- local categories=("agent" "command" "tool" "plugin" "context")
-
- for category in "${categories[@]}"; do
- local category_dir="$REPO_ROOT/.opencode/$category"
-
- if [ ! -d "$category_dir" ]; then
- continue
- fi
-
- # Find all .md and .ts files (excluding node_modules)
- while IFS= read -r file; do
- local rel_path="${file#"$REPO_ROOT"/}"
- # Skip node_modules
- if [[ "$rel_path" == *"/node_modules/"* ]]; then
- continue
- fi
-
- # Check if this path is in registry
- # shellcheck disable=SC2143
- if ! echo "$registry_paths" | grep -q "^${rel_path}$"; then
- ORPHANED_FILES=$((ORPHANED_FILES + 1))
- ORPHANED_COMPONENTS+=("$rel_path")
- [ "$VERBOSE" = true ] && print_warning "Orphaned file (not in registry): ${rel_path}"
- fi
- done < <(find "$category_dir" -type f \( -name "*.md" -o -name "*.ts" \) 2>/dev/null)
- done
- }
- #############################################################################
- # Reporting
- #############################################################################
- print_summary() {
- echo ""
- echo -e "${BOLD}═══════════════════════════════════════════════════════════════${NC}"
- echo -e "${BOLD}Validation Summary${NC}"
- echo -e "${BOLD}═══════════════════════════════════════════════════════════════${NC}"
- echo ""
- echo -e "Total paths checked: ${CYAN}${TOTAL_PATHS}${NC}"
- echo -e "Valid paths: ${GREEN}${VALID_PATHS}${NC}"
- echo -e "Missing paths: ${RED}${MISSING_PATHS}${NC}"
-
- if [ "$VERBOSE" = true ]; then
- echo -e "Orphaned files: ${YELLOW}${ORPHANED_FILES}${NC}"
- fi
-
- echo ""
-
- if [ $MISSING_PATHS -eq 0 ]; then
- print_success "All registry paths are valid!"
-
- if [ $ORPHANED_FILES -gt 0 ] && [ "$VERBOSE" = true ]; then
- echo ""
- print_warning "Found ${ORPHANED_FILES} orphaned file(s) not in registry"
- echo ""
- echo "Orphaned files:"
- for file in "${ORPHANED_COMPONENTS[@]}"; do
- echo " - $file"
- done
- echo ""
- echo "Consider adding these to registry.json or removing them."
- fi
-
- return 0
- else
- print_error "Found ${MISSING_PATHS} missing file(s)"
- echo ""
- echo "Missing files:"
- for entry in "${MISSING_FILES[@]}"; do
- IFS='|' read -r cat_id name path <<< "$entry"
- echo " - ${path} (${cat_id})"
- done
- echo ""
- echo "Please fix these issues before proceeding."
-
- if [ "$FIX_MODE" = false ]; then
- echo ""
- print_info "Run with --fix flag to see suggested fixes"
- fi
-
- return 1
- fi
- }
- #############################################################################
- # Main
- #############################################################################
- main() {
- # Parse arguments
- while [ $# -gt 0 ]; do
- case "$1" in
- -v|--verbose)
- VERBOSE=true
- shift
- ;;
- -f|--fix)
- FIX_MODE=true
- VERBOSE=true
- shift
- ;;
- -h|--help)
- usage
- ;;
- *)
- echo "Unknown option: $1"
- usage
- ;;
- esac
- done
-
- print_header
-
- # Check dependencies
- check_dependencies
-
- # Validate registry file
- validate_registry_file
-
- echo ""
- print_info "Validating component paths..."
- echo ""
-
- # Validate each category
- validate_component_paths "agents" "Agents"
- validate_component_paths "subagents" "Subagents"
- validate_component_paths "commands" "Commands"
- validate_component_paths "tools" "Tools"
- validate_component_paths "plugins" "Plugins"
- validate_component_paths "contexts" "Contexts"
- validate_component_paths "config" "Config"
-
- # Scan for orphaned files if verbose
- if [ "$VERBOSE" = true ]; then
- scan_for_orphaned_files
- fi
-
- # Print summary and exit with appropriate code
- if print_summary; then
- exit 0
- else
- exit 1
- fi
- }
- main "$@"
|