#!/bin/bash # validate-plugin.sh — Static validation for ms-ai-architect plugin # Usage: bash tests/validate-plugin.sh set -euo pipefail # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' PLUGIN_ROOT="$(cd "$(dirname "$0")/.." && pwd)" PASS=0 FAIL=0 WARN=0 pass() { echo -e "${GREEN} ✓ $1${NC}"; PASS=$((PASS + 1)); } fail() { echo -e "${RED} ✗ $1${NC}"; FAIL=$((FAIL + 1)); } warn() { echo -e "${YELLOW} ⚠ $1${NC}"; WARN=$((WARN + 1)); } echo "=== ms-ai-architect Plugin Validation ===" echo "Plugin root: $PLUGIN_ROOT" echo "" # ------------------------------------------------------- # Check 1: Agent Frontmatter # ------------------------------------------------------- echo "--- Check 1: Agent Frontmatter ---" VALID_MODELS="opus sonnet haiku" VALID_COLORS="blue green yellow purple cyan red orange magenta white" for agent_file in "$PLUGIN_ROOT"/agents/*.md; do [ -f "$agent_file" ] || continue basename_file="$(basename "$agent_file")" # Must have --- on line 1 first_line="$(head -n 1 "$agent_file")" if [ "$first_line" != "---" ]; then fail "$basename_file: missing frontmatter delimiter (---) on line 1" continue fi # Extract frontmatter (between first and second ---) frontmatter="$(sed -n '1,/^---$/{ /^---$/d; p; }' "$agent_file" | sed '1d')" # sed '1d' removes the first --- captured; we actually need lines between first and second --- # Redo: extract lines between line 2 and next --- frontmatter="$(awk 'NR==1{next} /^---$/{exit} {print}' "$agent_file")" # Check required fields for field in "name:" "description:" "model:" "color:" "tools:"; do if echo "$frontmatter" | grep -q "^${field}"; then pass "$basename_file: has $field" elif echo "$frontmatter" | grep -q "^ *${field}"; then # indented (part of multiline) - still counts for description pass "$basename_file: has $field" else # description can be multi-line with | if [ "$field" = "description:" ] && echo "$frontmatter" | grep -q "description:"; then pass "$basename_file: has $field" else fail "$basename_file: missing $field" fi fi done # Validate model value model_value="$(echo "$frontmatter" | grep "^model:" | sed 's/^model: *//' | tr -d '[:space:]')" if [ -n "$model_value" ]; then model_valid=false for m in $VALID_MODELS; do if [ "$model_value" = "$m" ]; then model_valid=true break fi done if $model_valid; then pass "$basename_file: model '$model_value' is valid" else fail "$basename_file: model '$model_value' is not valid (expected: $VALID_MODELS)" fi fi # Validate color value color_value="$(echo "$frontmatter" | grep "^color:" | sed 's/^color: *//' | tr -d '[:space:]')" if [ -n "$color_value" ]; then color_valid=false for c in $VALID_COLORS; do if [ "$color_value" = "$c" ]; then color_valid=true break fi done if $color_valid; then pass "$basename_file: color '$color_value' is valid" else fail "$basename_file: color '$color_value' is not valid (expected: $VALID_COLORS)" fi fi # Validate tools is a JSON array (starts with [) tools_line="$(echo "$frontmatter" | grep "^tools:" || true)" if [ -n "$tools_line" ]; then tools_value="$(echo "$tools_line" | sed 's/^tools: *//')" if echo "$tools_value" | grep -q '^\['; then pass "$basename_file: tools is a JSON array" else fail "$basename_file: tools is not a JSON array (got: $tools_value)" fi fi done echo "" # ------------------------------------------------------- # Check 2: Command Frontmatter # ------------------------------------------------------- echo "--- Check 2: Command Frontmatter ---" for cmd_file in "$PLUGIN_ROOT"/commands/*.md; do [ -f "$cmd_file" ] || continue basename_file="$(basename "$cmd_file")" basename_noext="${basename_file%.md}" # Must have --- on line 1 first_line="$(head -n 1 "$cmd_file")" if [ "$first_line" != "---" ]; then fail "$basename_file: missing frontmatter delimiter (---) on line 1" continue fi # Extract frontmatter frontmatter="$(awk 'NR==1{next} /^---$/{exit} {print}' "$cmd_file")" # Check required fields: name, description for field in "name:" "description:"; do if echo "$frontmatter" | grep -q "${field}"; then pass "$basename_file: has $field" else fail "$basename_file: missing $field" fi done # Check allowed-tools (warn if missing) if echo "$frontmatter" | grep -q "allowed-tools:"; then pass "$basename_file: has allowed-tools" else warn "$basename_file: missing allowed-tools (recommended)" fi # Validate name matches filename pattern (allows both "command" and "plugin:command") name_value="$(echo "$frontmatter" | grep "^name:" | sed 's/^name: *//' | tr -d '[:space:]')" name_suffix="${name_value##*:}" if [ -n "$name_value" ] && { [ "$name_value" = "$basename_noext" ] || [ "$name_suffix" = "$basename_noext" ]; }; then pass "$basename_file: name matches filename" elif [ -n "$name_value" ]; then fail "$basename_file: name '$name_value' does not match filename '$basename_noext'" fi done echo "" # ------------------------------------------------------- # Check 3: Encoding Validation # ------------------------------------------------------- echo "--- Check 3: Encoding Validation ---" encoding_issues=0 for dir in agents commands skills; do dir_path="$PLUGIN_ROOT/$dir" [ -d "$dir_path" ] || continue while IFS= read -r -d '' mdfile; do basename_file="$(basename "$mdfile")" rel_path="${mdfile#$PLUGIN_ROOT/}" # Check for broken UTF-8 sequences if grep -ql 'æ\|ø\|Ã¥\|Ã\†\|Ø\|Ã…' "$mdfile" 2>/dev/null; then fail "$rel_path: broken æ/ø/å encoding detected" encoding_issues=$((encoding_issues + 1)) fi if grep -ql 'â€"' "$mdfile" 2>/dev/null; then fail "$rel_path: broken em-dash/en-dash encoding detected" encoding_issues=$((encoding_issues + 1)) fi done < <(find "$dir_path" -name '*.md' -print0) done if [ "$encoding_issues" -eq 0 ]; then pass "No encoding issues found in agents/, commands/, skills/" fi echo "" # ------------------------------------------------------- # Check 4: KB Reference Validation # ------------------------------------------------------- echo "--- Check 4: KB Reference Validation ---" for agent_file in "$PLUGIN_ROOT"/agents/*.md; do [ -f "$agent_file" ] || continue basename_file="$(basename "$agent_file")" # Extract lines referencing references/ paths ref_paths="$(grep -o 'references/[a-zA-Z0-9_-]*/\?' "$agent_file" | sort -u || true)" if [ -z "$ref_paths" ]; then continue fi while IFS= read -r ref_path; do # Normalize: remove trailing slash, build full path relative to skill references ref_dir="$(echo "$ref_path" | sed 's|/$||')" # Check across all skill directories full_path="" for skill_dir in "$PLUGIN_ROOT"/skills/*/; do if [ -d "${skill_dir}${ref_dir}" ]; then full_path="${skill_dir}${ref_dir}" break fi done if [ -z "$full_path" ]; then full_path="$PLUGIN_ROOT/skills/ms-ai-engineering/$ref_dir" fi if [ -d "$full_path" ]; then # Check if directory has files file_count="$(find "$full_path" -maxdepth 1 -name '*.md' -type f | wc -l | tr -d ' ')" if [ "$file_count" -gt 0 ]; then pass "$basename_file: $ref_dir/ exists ($file_count files)" else warn "$basename_file: $ref_dir/ exists but is empty" fi else fail "$basename_file: referenced $ref_dir/ does not exist at $full_path" fi done <<< "$ref_paths" done echo "" # ------------------------------------------------------- # Check 5: Plugin.json Validation # ------------------------------------------------------- echo "--- Check 5: Plugin.json Validation ---" plugin_json="$PLUGIN_ROOT/.claude-plugin/plugin.json" if [ ! -f "$plugin_json" ]; then fail "plugin.json not found at .claude-plugin/plugin.json" else pass "plugin.json exists" # Check required fields for field in "name" "version" "description"; do if grep -q "\"$field\"" "$plugin_json"; then pass "plugin.json: has \"$field\"" else fail "plugin.json: missing \"$field\"" fi done # Check auto_discover: optional field (not required in ktg-plugin-marketplace format) if grep -q '"auto_discover"' "$plugin_json"; then auto_val="$(grep '"auto_discover"' "$plugin_json" | grep -o 'true\|false')" if [ "$auto_val" = "true" ]; then pass "plugin.json: auto_discover is true" else fail "plugin.json: auto_discover is not true (got: $auto_val)" fi else pass "plugin.json: auto_discover not present (auto-discovery via hooks.json)" fi fi echo "" # ------------------------------------------------------- # Summary # ------------------------------------------------------- echo "=== Results ===" echo -e "${GREEN}PASS: $PASS${NC}" echo -e "${RED}FAIL: $FAIL${NC}" echo -e "${YELLOW}WARN: $WARN${NC}" if [ $FAIL -gt 0 ]; then echo -e "${RED}VALIDATION FAILED${NC}" exit 1 else echo -e "${GREEN}VALIDATION PASSED${NC}" exit 0 fi