OpenWriterControle/scripts/tests/test-installer-files.sh
Darren Hinde 013682c499
fix: resolve installer failures and add comprehensive validation (#189)
* 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
2026-02-06 17:07:42 +00:00

363 lines
10 KiB
Bash
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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