Files
AGENTS/skill/agent-development/scripts/validate-agent.sh

305 lines
7.6 KiB
Bash
Raw Normal View History

#!/usr/bin/env bash
# Agent Configuration Validator
# Validates agent configurations in JSON or Markdown format
set -euo pipefail
usage() {
echo "Usage: $0 <path/to/agents.json | path/to/agent.md>"
echo ""
echo "Validates agent configuration for Opencode:"
echo " - JSON: Validates agents.json structure"
echo " - Markdown: Validates agent .md file with frontmatter"
echo ""
echo "Examples:"
echo " $0 agent/agents.json"
echo " $0 ~/.config/opencode/agents/review.md"
exit 1
}
validate_json() {
local file="$1"
echo "🔍 Validating JSON agent configuration: $file"
echo ""
# Check JSON syntax
if ! python3 -c "import json; json.load(open('$file'))" 2>/dev/null; then
echo "❌ Invalid JSON syntax"
exit 1
fi
echo "✅ Valid JSON syntax"
# Parse and validate each agent
local error_count=0
local warning_count=0
# Get agent names
local agents
agents=$(python3 -c "
import json
import sys
with open('$file') as f:
data = json.load(f)
# Handle both formats: direct agents or nested under 'agent' key
if 'agent' in data:
agents = data['agent']
else:
agents = data
for name in agents.keys():
print(name)
")
if [ -z "$agents" ]; then
echo "❌ No agents found in configuration"
exit 1
fi
echo ""
echo "Found agents: $agents"
echo ""
# Validate each agent
for agent_name in $agents; do
echo "Checking agent: $agent_name"
local validation_result
validation_result=$(python3 -c "
import json
import sys
with open('$file') as f:
data = json.load(f)
# Handle both formats
if 'agent' in data:
agents = data['agent']
else:
agents = data
agent = agents.get('$agent_name', {})
errors = []
warnings = []
# Check required field: description
if 'description' not in agent:
errors.append('Missing required field: description')
elif len(agent['description']) < 10:
warnings.append('Description is very short (< 10 chars)')
# Check mode if present
mode = agent.get('mode', 'all')
if mode not in ['primary', 'subagent', 'all']:
errors.append(f'Invalid mode: {mode} (must be primary, subagent, or all)')
# Check model format if present
model = agent.get('model', '')
if model and '/' not in model:
warnings.append(f'Model should use provider/model-id format: {model}')
# Check temperature if present
temp = agent.get('temperature')
if temp is not None:
if not isinstance(temp, (int, float)):
errors.append(f'Temperature must be a number: {temp}')
elif temp < 0 or temp > 2:
warnings.append(f'Temperature {temp} is outside typical range (0-1)')
# Check prompt
prompt = agent.get('prompt', '')
if prompt:
if prompt.startswith('{file:') and not prompt.endswith('}'):
errors.append('Invalid file reference syntax in prompt')
elif 'prompt' not in agent:
warnings.append('No prompt defined (will use default)')
# Check tools if present
tools = agent.get('tools', {})
if tools and not isinstance(tools, dict):
errors.append('Tools must be an object')
# Check permission if present
permission = agent.get('permission', {})
if permission and not isinstance(permission, dict):
errors.append('Permission must be an object')
# Output results
for e in errors:
print(f'ERROR:{e}')
for w in warnings:
print(f'WARNING:{w}')
if not errors and not warnings:
print('OK')
")
while IFS= read -r line; do
if [[ "$line" == ERROR:* ]]; then
echo "${line#ERROR:}"
((error_count++))
elif [[ "$line" == WARNING:* ]]; then
echo " ⚠️ ${line#WARNING:}"
((warning_count++))
elif [[ "$line" == "OK" ]]; then
echo " ✅ Valid"
fi
done <<< "$validation_result"
done
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
if [ $error_count -eq 0 ] && [ $warning_count -eq 0 ]; then
echo "✅ All agents validated successfully!"
exit 0
elif [ $error_count -eq 0 ]; then
echo "⚠️ Validation passed with $warning_count warning(s)"
exit 0
else
echo "❌ Validation failed with $error_count error(s) and $warning_count warning(s)"
exit 1
fi
}
validate_markdown() {
local file="$1"
echo "🔍 Validating Markdown agent file: $file"
echo ""
# Check file exists
if [ ! -f "$file" ]; then
echo "❌ File not found: $file"
exit 1
fi
echo "✅ File exists"
# Check starts with ---
local first_line
first_line=$(head -1 "$file")
if [ "$first_line" != "---" ]; then
echo "❌ File must start with YAML frontmatter (---)"
exit 1
fi
echo "✅ Starts with frontmatter"
# Check has closing ---
if ! tail -n +2 "$file" | grep -q '^---$'; then
echo "❌ Frontmatter not closed (missing second ---)"
exit 1
fi
echo "✅ Frontmatter properly closed"
local error_count=0
local warning_count=0
# Extract and validate frontmatter
local frontmatter
frontmatter=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$file")
# Check description (required)
if ! echo "$frontmatter" | grep -q '^description:'; then
echo "❌ Missing required field: description"
((error_count++))
else
echo "✅ description: present"
fi
# Check mode if present
local mode
mode=$(echo "$frontmatter" | grep '^mode:' | sed 's/mode: *//' || true)
if [ -n "$mode" ]; then
case "$mode" in
primary|subagent|all)
echo "✅ mode: $mode"
;;
*)
echo "❌ Invalid mode: $mode (must be primary, subagent, or all)"
((error_count++))
;;
esac
else
echo "💡 mode: not specified (defaults to 'all')"
fi
# Check model if present
local model
model=$(echo "$frontmatter" | grep '^model:' | sed 's/model: *//' || true)
if [ -n "$model" ]; then
if [[ "$model" == */* ]]; then
echo "✅ model: $model"
else
echo "⚠️ model should use provider/model-id format: $model"
((warning_count++))
fi
else
echo "💡 model: not specified (will inherit)"
fi
# Check temperature if present
local temp
temp=$(echo "$frontmatter" | grep '^temperature:' | sed 's/temperature: *//' || true)
if [ -n "$temp" ]; then
echo "✅ temperature: $temp"
fi
# Check system prompt (body after frontmatter)
local system_prompt
system_prompt=$(awk '/^---$/{i++; next} i>=2' "$file")
if [ -z "$system_prompt" ]; then
echo "⚠️ System prompt (body) is empty"
((warning_count++))
else
local prompt_length=${#system_prompt}
echo "✅ System prompt: $prompt_length characters"
if [ $prompt_length -lt 50 ]; then
echo "⚠️ System prompt is very short"
((warning_count++))
fi
if ! echo "$system_prompt" | grep -q "You are\|You will\|Your"; then
echo "⚠️ System prompt should use second person (You are..., You will...)"
((warning_count++))
fi
fi
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
if [ $error_count -eq 0 ] && [ $warning_count -eq 0 ]; then
echo "✅ Validation passed!"
exit 0
elif [ $error_count -eq 0 ]; then
echo "⚠️ Validation passed with $warning_count warning(s)"
exit 0
else
echo "❌ Validation failed with $error_count error(s) and $warning_count warning(s)"
exit 1
fi
}
# Main
if [ $# -eq 0 ]; then
usage
fi
FILE="$1"
if [ ! -f "$FILE" ]; then
echo "❌ File not found: $FILE"
exit 1
fi
# Determine file type and validate
if [[ "$FILE" == *.json ]]; then
validate_json "$FILE"
elif [[ "$FILE" == *.md ]]; then
validate_markdown "$FILE"
else
echo "❌ Unknown file type. Expected .json or .md"
echo ""
usage
fi