OpenWriterControle/scripts/auto-detect-components.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

330 lines
10 KiB
Bash
Executable file
Raw Permalink 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
#############################################################################
# Auto-Detect Components Script
# Scans .opencode directory for new components not in registry
# Suggests additions with proper metadata
#############################################################################
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)"
AUTO_ADD=false
DRY_RUN=false
# Arrays to store new components
declare -a NEW_COMPONENTS
#############################################################################
# Utility Functions
#############################################################################
print_header() {
echo -e "${CYAN}${BOLD}"
echo "╔════════════════════════════════════════════════════════════════╗"
echo "║ ║"
echo "║ Auto-Detect Components 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 " -a, --auto-add Automatically add new components to registry"
echo " -d, --dry-run Show what would be added without modifying registry"
echo " -h, --help Show this help message"
echo ""
exit 0
}
#############################################################################
# Component Detection
#############################################################################
extract_metadata_from_file() {
local file=$1
local id=""
local name=""
local description=""
# Try to extract from frontmatter (YAML)
if grep -q "^---$" "$file" 2>/dev/null; then
# Extract description from frontmatter
description=$(sed -n '/^---$/,/^---$/p' "$file" | grep "^description:" | sed 's/description: *"\?\(.*\)"\?/\1/' | head -1)
fi
# If no description in frontmatter, try to get from first heading or paragraph
if [ -z "$description" ]; then
description=$(grep -m 1 "^# " "$file" | sed 's/^# //' || echo "")
fi
# Generate ID from filename
local filename
filename=$(basename "$file" .md)
id=$(echo "$filename" | tr '[:upper:]' '[:lower:]' | tr ' ' '-')
# Generate name from filename (capitalize words)
name=$(echo "$filename" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) tolower(substr($i,2))}1')
echo "${id}|${name}|${description}"
}
detect_component_type() {
local path=$1
if [[ "$path" == *"/agent/subagents/"* ]]; then
echo "subagent"
elif [[ "$path" == *"/agent/"* ]]; then
echo "agent"
elif [[ "$path" == *"/command/"* ]]; then
echo "command"
elif [[ "$path" == *"/tool/"* ]]; then
echo "tool"
elif [[ "$path" == *"/plugin/"* ]]; then
echo "plugin"
elif [[ "$path" == *"/context/"* ]]; then
echo "context"
else
echo "unknown"
fi
}
get_registry_key() {
local type=$1
case "$type" in
config) echo "config" ;;
*) echo "${type}s" ;;
esac
}
scan_for_new_components() {
print_info "Scanning for new components..."
echo ""
# 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
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 files (excluding node_modules, tests, docs)
while IFS= read -r file; do
local rel_path="${file#$REPO_ROOT/}"
# Skip node_modules, tests, docs, templates
if [[ "$rel_path" == *"/node_modules/"* ]] || \
[[ "$rel_path" == *"/tests/"* ]] || \
[[ "$rel_path" == *"/docs/"* ]] || \
[[ "$rel_path" == *"/template"* ]] || \
[[ "$rel_path" == *"README.md" ]] || \
[[ "$rel_path" == *"index.md" ]]; then
continue
fi
# Check if this path is in registry
# shellcheck disable=SC2143
if ! echo "$registry_paths" | grep -q "^${rel_path}$"; then
# Extract metadata
local metadata
metadata=$(extract_metadata_from_file "$file")
IFS='|' read -r id name description <<< "$metadata"
# Detect component type
local comp_type
comp_type=$(detect_component_type "$rel_path")
if [ "$comp_type" != "unknown" ]; then
NEW_COMPONENTS+=("${comp_type}|${id}|${name}|${description}|${rel_path}")
print_warning "New ${comp_type}: ${name} (${id})"
echo " Path: ${rel_path}"
[ -n "$description" ] && echo " Description: ${description}"
echo ""
fi
fi
done < <(find "$category_dir" -type f -name "*.md" 2>/dev/null)
done
}
add_component_to_registry() {
local comp_type=$1
local id=$2
local name=$3
local description=$4
local path=$5
# Default description if empty
if [ -z "$description" ]; then
description="Component: ${name}"
fi
# Escape quotes and special characters in description
description=$(echo "$description" | sed 's/"/\\"/g' | sed "s/'/\\'/g")
# Get registry key (agents, subagents, commands, etc.)
local registry_key
registry_key=$(get_registry_key "$comp_type")
# Use jq to properly construct JSON (avoids escaping issues)
local temp_file="${REGISTRY_FILE}.tmp"
jq --arg id "$id" \
--arg name "$name" \
--arg type "$comp_type" \
--arg path "$path" \
--arg desc "$description" \
".components.${registry_key} += [{
\"id\": \$id,
\"name\": \$name,
\"type\": \$type,
\"path\": \$path,
\"description\": \$desc,
\"tags\": [],
\"dependencies\": [],
\"category\": \"standard\"
}]" "$REGISTRY_FILE" > "$temp_file"
if [ $? -eq 0 ]; then
mv "$temp_file" "$REGISTRY_FILE"
print_success "Added ${comp_type}: ${name}"
else
print_error "Failed to add ${comp_type}: ${name}"
rm -f "$temp_file"
return 1
fi
}
#############################################################################
# Main
#############################################################################
main() {
# Parse arguments
while [ $# -gt 0 ]; do
case "$1" in
-a|--auto-add)
AUTO_ADD=true
shift
;;
-d|--dry-run)
DRY_RUN=true
shift
;;
-h|--help)
usage
;;
*)
echo "Unknown option: $1"
usage
;;
esac
done
print_header
# Check dependencies
if ! command -v jq &> /dev/null; then
print_error "jq is required but not installed"
exit 1
fi
# Validate registry file
if [ ! -f "$REGISTRY_FILE" ]; then
print_error "Registry file not found: $REGISTRY_FILE"
exit 1
fi
if ! jq empty "$REGISTRY_FILE" 2>/dev/null; then
print_error "Registry file is not valid JSON"
exit 1
fi
# Scan for new components
scan_for_new_components
# Summary
echo ""
echo -e "${BOLD}═══════════════════════════════════════════════════════════════${NC}"
echo -e "${BOLD}Summary${NC}"
echo -e "${BOLD}═══════════════════════════════════════════════════════════════${NC}"
echo ""
if [ ${#NEW_COMPONENTS[@]} -eq 0 ]; then
print_success "No new components found. Registry is up to date!"
exit 0
fi
echo -e "Found ${YELLOW}${#NEW_COMPONENTS[@]}${NC} new component(s)"
echo ""
# Add components if auto-add is enabled
if [ "$AUTO_ADD" = true ] && [ "$DRY_RUN" = false ]; then
print_info "Adding new components to registry..."
echo ""
local added=0
for entry in "${NEW_COMPONENTS[@]}"; do
IFS='|' read -r comp_type id name description path <<< "$entry"
if add_component_to_registry "$comp_type" "$id" "$name" "$description" "$path"; then
added=$((added + 1))
fi
done
# Update timestamp
jq '.metadata.lastUpdated = (now | strftime("%Y-%m-%d"))' "$REGISTRY_FILE" > "${REGISTRY_FILE}.tmp"
mv "${REGISTRY_FILE}.tmp" "$REGISTRY_FILE"
echo ""
print_success "Added ${added} component(s) to registry"
elif [ "$DRY_RUN" = true ]; then
print_info "Dry run mode - no changes made to registry"
echo ""
echo "Run without --dry-run to add these components"
else
print_info "Run with --auto-add to add these components to registry"
echo ""
echo "Or manually add them to registry.json"
fi
exit 0
}
main "$@"