* fix: ContextScout falls back to global core/ when local core/ missing Users who install OAC globally have core standards at ~/.config/opencode/context/core/ but no local core/ directory. ContextScout now does a one-time check on startup: if local core/navigation.md doesn't exist, it checks the global path from paths.json. This resolves the ENOENT errors when agents try to read core/standards/ files that only exist globally. - paths.json: enabled global path (~/.config/opencode/context) - contextscout.md: added global_fallback rule with 2-glob cap - Fallback is ONLY for core/ files, never project-intelligence * docs: add context resolution explanation to README Explains how ContextScout resolves context files: local-first, global fallback only for core/ files, project-intelligence always local. Includes flow diagram, key rules, and common setup table. * fix: resolve installer failures and add comprehensive validation ## Summary Fixes critical installer failures where ContextScout and other components couldn't find their configuration files after installation. Adds comprehensive validation system to prevent future issues. ## Problems Fixed ### 1. Missing Critical Files (13 dead registry references) Files were referenced in registry but didn't exist on disk: - workflows-delegation (split into 3 files) - design-iteration (split into 8 files) - animation-patterns (split into 6 files) - adding-agent (split into 2 files) - adding-skill (split into 3 files) - navigation-design (split into 2 files) - external-libraries (split into 3 files) - claude-code-docs/* (directory removed) ### 2. Missing Infrastructure Files ContextScout failed after installation because 2 critical files weren't in registry: - paths.json - Context root configuration - navigation.md - Root navigation for discovery ### 3. Profile Inconsistencies Profiles referenced components that don't exist: - context:adding-skill → context:adding-skill-basics - context:animation-patterns → animation-basics/components/advanced - context:design-assets (removed - doesn't exist) - context:to-be-consumed/* (removed - directory doesn't exist) - skill:context-manager → subagent:context-manager ## Changes Made ### Registry (registry.json) - Removed 13 dead references - Added 27 new split-file entries - Added 2 critical infrastructure entries - Fixed all 5 profile configurations - Updated ContextScout dependencies - Updated agent-metadata.json dependencies ### New Validation Scripts - scripts/registry/check-dependencies.ts - Comprehensive dependency checker - scripts/tests/test-installer-files.sh - Installer simulation test - scripts/registry/fix-registry.py - Automated registry fixer - scripts/hooks/pre-commit - Git pre-commit hook ### Documentation - docs/maintenance/REGISTRY_FIXES_SUMMARY.md - docs/maintenance/CONTEXTSCOUT_FIX.md - docs/maintenance/REGISTRY_VALIDATION.md ## Validation Results Before: - 13 missing files in registry - 78 orphaned files on disk - 3 missing dependencies - Installer would fail After: - 244 total paths, 0 missing - 262 dependency checks, 0 failures - 0 orphaned dependencies - All profiles consistent - Installer works correctly ## Testing ```bash # Registry validation bun run scripts/registry/validate-registry.ts # ✓ All 244 paths valid # Dependency checking bun run scripts/registry/check-dependencies.ts # ✓ All 262 checks passed # Installer test ./scripts/tests/test-installer-files.sh --local --profile=essential # ✓ All files accessible ``` ## Usage ### Run validation before commits: ```bash bun run scripts/registry/check-dependencies.ts ``` ### Install pre-commit hook: ```bash cp scripts/hooks/pre-commit .git/hooks/pre-commit chmod +x .git/hooks/pre-commit ``` ### Test installer: ```bash ./scripts/tests/test-installer-files.sh --local --profile=essential ``` ## Impact This fix ensures: - ✓ Installer downloads all required files - ✓ ContextScout works immediately after install - ✓ No broken dependencies - ✓ No missing profile components - ✓ Future issues caught by validation ## Breaking Changes None - all changes are additive or fix broken references. ## Related - Fixes: ContextScout failing after installation - Prevents: Missing file errors during install - Adds: Comprehensive validation system * fix: resolve shellcheck warnings in test scripts - Remove unused REPO_URL variable from test-installer-files.sh - Add shellcheck disable comments for cleanup functions used via trap - Fixes CI failures in ShellCheck Analysis workflow * fix: resolve shellcheck errors across all scripts Fixed arithmetic expansion issues and code quality warnings: - Changed ((var++)) to POSIX-compliant 1 syntax - Fixed unquoted variables and grep pipe issues (SC2002, SC2143) - Added shellcheck disable comments for intentional patterns - Fixed variable initialization and return statement placement Scripts fixed: - scripts/tests/*.sh (5 files) - scripts/registry/*.sh (4 files) - scripts/validation/*.sh (2 files) - scripts/*.sh (2 files) - evals/framework/scripts/*.sh (2 files) All scripts now pass shellcheck --severity=warning * chore: trigger CI shellcheck validation
363 lines
10 KiB
Bash
Executable file
363 lines
10 KiB
Bash
Executable file
#!/usr/bin/env bash
|
||
###############################################################################
|
||
# Installer Test Script
|
||
# Simulates the installer and shows which files would fail to download
|
||
# Usage: ./scripts/tests/test-installer-files.sh [--profile=<profile>] [--verbose]
|
||
###############################################################################
|
||
|
||
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
|
||
BRANCH="main"
|
||
RAW_URL="https://raw.githubusercontent.com/darrenhinde/OpenAgentsControl/${BRANCH}"
|
||
TEMP_DIR="/tmp/opencode-installer-test-$$"
|
||
|
||
# Allow local testing (useful for CI/CD or when testing changes before pushing)
|
||
USE_LOCAL=false
|
||
LOCAL_REGISTRY="./registry.json"
|
||
|
||
# Global variables
|
||
VERBOSE=false
|
||
TEST_PROFILE=""
|
||
FAILED_FILES=()
|
||
SKIPPED_FILES=()
|
||
SUCCESS_COUNT=0
|
||
FAIL_COUNT=0
|
||
|
||
# Cleanup function
|
||
# shellcheck disable=SC2329
|
||
cleanup() {
|
||
rm -rf "$TEMP_DIR" 2>/dev/null || true
|
||
}
|
||
|
||
trap cleanup EXIT INT TERM
|
||
|
||
# Print functions
|
||
print_header() {
|
||
echo -e "${CYAN}${BOLD}"
|
||
echo "╔════════════════════════════════════════════════════════════════╗"
|
||
echo "║ ║"
|
||
echo "║ Installer File Test - 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"
|
||
}
|
||
|
||
print_step() {
|
||
echo -e "\n${BOLD}$1${NC}"
|
||
}
|
||
|
||
# Check if URL is accessible
|
||
check_url() {
|
||
local url="$1"
|
||
local component="$2"
|
||
|
||
if curl -fsSL --max-time 10 -I "$url" > /dev/null 2>&1; then
|
||
return 0
|
||
else
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
# Test a single file
|
||
test_file() {
|
||
local path="$1"
|
||
local component="$2"
|
||
local url="${RAW_URL}/${path}"
|
||
|
||
if check_url "$url" "$component"; then
|
||
SUCCESS_COUNT=$((SUCCESS_COUNT + 1))
|
||
if [ "$VERBOSE" = true ]; then
|
||
print_success "Accessible: ${path}"
|
||
fi
|
||
return 0
|
||
else
|
||
FAIL_COUNT=$((FAIL_COUNT + 1))
|
||
FAILED_FILES+=("${component}|${path}")
|
||
print_error "NOT FOUND: ${path}"
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
# Test components from a category
|
||
test_category() {
|
||
local category="$1"
|
||
local registry_file="$2"
|
||
|
||
print_step "Testing ${category}..."
|
||
|
||
local components
|
||
components=$(jq -r ".components.${category}[]? | \"\(.id)|\(.path)\"" "$registry_file" 2>/dev/null || echo "")
|
||
|
||
if [ -z "$components" ]; then
|
||
print_warning "No components in category: ${category}"
|
||
return
|
||
fi
|
||
|
||
while IFS='|' read -r id path; do
|
||
[ -z "$id" ] && continue
|
||
test_file "$path" "${category}:${id}"
|
||
done <<< "$components"
|
||
}
|
||
|
||
# Test profile components
|
||
test_profile() {
|
||
local profile="$1"
|
||
local registry_file="$2"
|
||
|
||
print_step "Testing Profile: ${profile}"
|
||
|
||
# Get components from profile
|
||
local components
|
||
components=$(jq -r ".profiles.${profile}.components[]?" "$registry_file" 2>/dev/null || echo "")
|
||
|
||
if [ -z "$components" ]; then
|
||
print_error "Profile not found or empty: ${profile}"
|
||
return 1
|
||
fi
|
||
|
||
print_info "Profile components:"
|
||
|
||
while IFS= read -r component; do
|
||
[ -z "$component" ] && continue
|
||
|
||
local type="${component%%:*}"
|
||
local id="${component##*:}"
|
||
|
||
# Handle wildcards
|
||
if [[ "$id" == *"*"* ]]; then
|
||
print_info " Wildcard: ${component} (expanding...)"
|
||
|
||
if [ "$type" = "context" ]; then
|
||
local prefix="${id%%\**}"
|
||
prefix="${prefix%/}"
|
||
|
||
# Find matching contexts
|
||
local matches
|
||
matches=$(jq -r ".components.contexts[]? | select(.path | startswith(\".opencode/context/${prefix}\")) | \"\(.id)|\(.path)\"" "$registry_file")
|
||
|
||
while IFS='|' read -r match_id match_path; do
|
||
[ -z "$match_id" ] && continue
|
||
test_file "$match_path" "context:${match_id}"
|
||
done <<< "$matches"
|
||
fi
|
||
continue
|
||
fi
|
||
|
||
# Get path for component
|
||
local path
|
||
path=$(jq -r ".components.${type}s[]? | select(.id == \"${id}\") | .path" "$registry_file")
|
||
|
||
if [ -n "$path" ] && [ "$path" != "null" ]; then
|
||
test_file "$path" "${component}"
|
||
else
|
||
print_warning " Component not found in registry: ${component}"
|
||
SKIPPED_FILES+=("${component}|not_in_registry")
|
||
fi
|
||
done <<< "$components"
|
||
}
|
||
|
||
# Test all categories
|
||
test_all() {
|
||
local registry_file="$1"
|
||
|
||
print_step "Testing All Component Categories"
|
||
|
||
local categories=("agents" "subagents" "commands" "tools" "plugins" "skills" "contexts" "config")
|
||
|
||
for category in "${categories[@]}"; do
|
||
test_category "$category" "$registry_file"
|
||
done
|
||
}
|
||
|
||
# Print summary
|
||
print_summary() {
|
||
echo ""
|
||
echo -e "${BOLD}═══════════════════════════════════════════════════════════════${NC}"
|
||
echo -e "${BOLD}Test Summary${NC}"
|
||
echo -e "${BOLD}═══════════════════════════════════════════════════════════════${NC}"
|
||
echo ""
|
||
echo "Total files tested: ${CYAN}$((SUCCESS_COUNT + FAIL_COUNT))${NC}"
|
||
echo "Files accessible: ${GREEN}${SUCCESS_COUNT}${NC}"
|
||
echo "Files NOT FOUND: ${RED}${FAIL_COUNT}${NC}"
|
||
|
||
if [ ${#SKIPPED_FILES[@]} -gt 0 ]; then
|
||
echo "Skipped: ${YELLOW}${#SKIPPED_FILES[@]}${NC}"
|
||
fi
|
||
|
||
echo ""
|
||
|
||
if [ $FAIL_COUNT -gt 0 ]; then
|
||
print_error "Found ${FAIL_COUNT} file(s) that would FAIL during installation:"
|
||
echo ""
|
||
echo "Failed files:"
|
||
for entry in "${FAILED_FILES[@]}"; do
|
||
local component="${entry%%|*}"
|
||
local path="${entry##*|}"
|
||
echo " - ${component}"
|
||
echo " URL: ${RAW_URL}/${path}"
|
||
done
|
||
echo ""
|
||
echo "${YELLOW}These files need to be:${NC}"
|
||
echo " 1. Added to the repository, OR"
|
||
echo " 2. Removed from registry.json"
|
||
echo ""
|
||
return 1
|
||
else
|
||
print_success "All files are accessible and would install successfully!"
|
||
return 0
|
||
fi
|
||
}
|
||
|
||
# Usage
|
||
usage() {
|
||
echo "Usage: $0 [OPTIONS]"
|
||
echo ""
|
||
echo "Options:"
|
||
echo " --profile=<name> Test a specific profile (essential, developer, business, full, advanced)"
|
||
echo " --all Test all components (default)"
|
||
echo " --verbose Show successful file checks"
|
||
echo " --local Use local registry.json instead of downloading from GitHub"
|
||
echo " --help Show this help message"
|
||
echo ""
|
||
echo "Examples:"
|
||
echo " $0 # Test all components (remote registry)"
|
||
echo " $0 --profile=essential # Test essential profile"
|
||
echo " $0 --profile=developer --verbose # Test developer profile with details"
|
||
echo " $0 --local # Test using local registry.json"
|
||
echo ""
|
||
exit 0
|
||
}
|
||
|
||
# Main
|
||
main() {
|
||
print_header
|
||
|
||
# Parse arguments
|
||
TEST_MODE="all"
|
||
|
||
while [ $# -gt 0 ]; do
|
||
case "$1" in
|
||
--profile=*)
|
||
TEST_PROFILE="${1#*=}"
|
||
TEST_MODE="profile"
|
||
;;
|
||
--profile)
|
||
if [ -n "$2" ]; then
|
||
TEST_PROFILE="$2"
|
||
TEST_MODE="profile"
|
||
shift
|
||
else
|
||
echo "Error: --profile requires a profile name"
|
||
exit 1
|
||
fi
|
||
;;
|
||
--all)
|
||
TEST_MODE="all"
|
||
;;
|
||
--verbose)
|
||
VERBOSE=true
|
||
;;
|
||
--local)
|
||
USE_LOCAL=true
|
||
;;
|
||
--help|-h)
|
||
usage
|
||
;;
|
||
*)
|
||
echo "Unknown option: $1"
|
||
echo "Run '$0 --help' for usage information"
|
||
exit 1
|
||
;;
|
||
esac
|
||
shift
|
||
done
|
||
|
||
# Check dependencies
|
||
if ! command -v curl &> /dev/null; then
|
||
print_error "curl is required but not installed"
|
||
exit 1
|
||
fi
|
||
|
||
if ! command -v jq &> /dev/null; then
|
||
print_error "jq is required but not installed"
|
||
exit 1
|
||
fi
|
||
|
||
# Create temp directory
|
||
mkdir -p "$TEMP_DIR"
|
||
|
||
# Get registry (local or remote)
|
||
if [ "$USE_LOCAL" = true ]; then
|
||
print_step "Using local registry..."
|
||
if [ ! -f "$LOCAL_REGISTRY" ]; then
|
||
print_error "Local registry not found: $LOCAL_REGISTRY"
|
||
exit 1
|
||
fi
|
||
cp "$LOCAL_REGISTRY" "${TEMP_DIR}/registry.json"
|
||
print_success "Local registry loaded"
|
||
else
|
||
print_step "Downloading registry from GitHub..."
|
||
if ! curl -fsSL "${RAW_URL}/registry.json" -o "${TEMP_DIR}/registry.json"; then
|
||
print_error "Failed to download registry from ${RAW_URL}/registry.json"
|
||
exit 1
|
||
fi
|
||
print_success "Registry downloaded successfully"
|
||
fi
|
||
|
||
# Validate registry JSON
|
||
if ! jq empty "${TEMP_DIR}/registry.json" 2>/dev/null; then
|
||
print_error "Registry is not valid JSON"
|
||
exit 1
|
||
fi
|
||
|
||
# Run tests
|
||
echo ""
|
||
print_info "Testing file accessibility from: ${RAW_URL}"
|
||
echo ""
|
||
|
||
case "$TEST_MODE" in
|
||
profile)
|
||
if [ -z "$TEST_PROFILE" ]; then
|
||
print_error "No profile specified. Use --profile=<name>"
|
||
exit 1
|
||
fi
|
||
test_profile "$TEST_PROFILE" "${TEMP_DIR}/registry.json"
|
||
;;
|
||
all)
|
||
test_all "${TEMP_DIR}/registry.json"
|
||
;;
|
||
esac
|
||
|
||
# Print summary
|
||
print_summary
|
||
exit $?
|
||
}
|
||
|
||
main "$@"
|
||
# Shellcheck validation passed
|