#!/bin/bash # Spec Author Validation Script # Validates specification documents against required templates and standards set -o pipefail SPEC_FILE="${1:-.}" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # Find git repo root for reliable path resolution # This allows scripts to work when called from any directory in the repo if git -C "$SCRIPT_DIR" rev-parse --git-dir > /dev/null 2>&1; then PROJECT_ROOT="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel)" else # Fallback to relative path traversal (4 levels up from scripts/) # From: project-root/project-basics/skills/spec-author/scripts/ # To: project-root/ PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)" fi # Templates are located in the spec-author plugin directory TEMPLATES_DIR="$SCRIPT_DIR/../templates" # Check if file is a template file is_template_file() { local basename=$(basename "$SPEC_FILE") # Check if file matches any template filename [[ "$basename" == "api-contract.md" ]] || [[ "$basename" == "business-requirement.md" ]] || [[ "$basename" == "component.md" ]] || [[ "$basename" == "configuration-schema.md" ]] || [[ "$basename" == "constitution.md" ]] || [[ "$basename" == "data-model.md" ]] || [[ "$basename" == "deployment-procedure.md" ]] || [[ "$basename" == "design-document.md" ]] || [[ "$basename" == "flow-schematic.md" ]] || [[ "$basename" == "milestone.md" ]] || [[ "$basename" == "plan.md" ]] || [[ "$basename" == "technical-requirement.md" ]] } # Color codes RED='\033[0;31m' YELLOW='\033[1;33m' GREEN='\033[0;32m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Check if file exists if [ ! -f "$SPEC_FILE" ]; then echo -e "${RED}✗ Error: Spec file not found: $SPEC_FILE${NC}" exit 1 fi # Skip validation for template files themselves if is_template_file; then echo -e "${BLUE}📋 SPEC VALIDATION REPORT${NC}" echo "====================" echo "" echo "Spec: $(basename "$SPEC_FILE")" echo "Status: ${GREEN}✓ TEMPLATE${NC} (templates are not validated, they are used as references)" echo "" exit 0 fi # Determine spec type from filename or spec ID determine_spec_type() { local filename=$(basename "$SPEC_FILE" .md) # First check by spec ID prefix in filename case "$filename" in brd-*) echo "business"; return ;; prd-*) echo "technical"; return ;; des-*) echo "design"; return ;; api-*) echo "api"; return ;; data-*) echo "data"; return ;; cmp-*) echo "component"; return ;; pln-*) echo "plan"; return ;; mls-*) echo "milestone"; return ;; flow-*) echo "flow"; return ;; deploy-*) echo "deployment"; return ;; config-*) echo "config"; return ;; esac # Then check by full template name if [[ "$filename" == "design-document" ]] || [[ "$SPEC_FILE" =~ design-document ]]; then echo "design" elif [[ "$filename" == "technical-requirement" ]] || [[ "$SPEC_FILE" =~ technical-requirement ]]; then echo "technical" elif [[ "$filename" == "business-requirement" ]] || [[ "$SPEC_FILE" =~ business-requirement ]]; then echo "business" elif [[ "$filename" == "api-contract" ]] || [[ "$SPEC_FILE" =~ api-contract ]]; then echo "api" elif [[ "$filename" == "data-model" ]] || [[ "$SPEC_FILE" =~ data-model ]]; then echo "data" elif [[ "$filename" == "component" ]] || [[ "$SPEC_FILE" =~ component ]]; then echo "component" elif [[ "$filename" == "plan" ]] || [[ "$SPEC_FILE" =~ plan ]]; then echo "plan" elif [[ "$filename" == "milestone" ]] || [[ "$SPEC_FILE" =~ milestone ]]; then echo "milestone" elif [[ "$filename" == "flow-schematic" ]] || [[ "$SPEC_FILE" =~ flow-schematic ]]; then echo "flow" elif [[ "$filename" == "deployment-procedure" ]] || [[ "$SPEC_FILE" =~ deployment-procedure ]]; then echo "deployment" elif [[ "$filename" == "configuration-schema" ]] || [[ "$SPEC_FILE" =~ configuration-schema ]]; then echo "config" else echo "unknown" fi } # Get required sections for spec type get_required_sections() { local spec_type=$1 case $spec_type in design) echo "Executive Summary|Problem Statement|Goals & Success Criteria|Proposed Solution|Implementation Plan|Risk & Mitigation" ;; technical) echo "Overview|Requirements|Constraints|Success Criteria" ;; business) echo "Description|Business Value|Stakeholders|User Stories|Acceptance Criteria" ;; api) echo "Endpoints|Request/Response|Authentication|Error Handling" ;; data) echo "Entities|Fields|Relationships|Constraints" ;; component) echo "Description|Responsibilities|Interfaces|Configuration" ;; plan) echo "Overview|Phases|Deliverables|Success Metrics" ;; milestone) echo "Milestone|Deliverables|Success Criteria" ;; flow) echo "Overview|Process Flow|Steps|Decision Points" ;; deployment) echo "Prerequisites|Instructions|Rollback|Monitoring" ;; config) echo "Schema|Fields|Validation|Examples" ;; *) echo "Title" ;; esac } # Count lines and TODOs count_todos() { grep -o "TODO" "$SPEC_FILE" | wc -l } # Count TODO items more accurately count_todo_items() { local count count=$(grep -cE "(TODO|' | grep -v '^$' | head -1) if [ -z "$non_comment_content" ]; then template_only_sections+=("$section") fi done < <(echo "$required_sections" | tr '|' '\n') printf '%s\n' "${template_only_sections[@]}" } # Calculate completion percentage calculate_completion() { local spec_type=$(determine_spec_type) local required_sections=$(get_required_sections "$spec_type") local total_required=$(echo "$required_sections" | tr '|' '\n' | wc -l) local found_sections=0 while IFS= read -r section; do if grep -qE "^## $section" "$SPEC_FILE"; then ((found_sections++)) fi done < <(echo "$required_sections" | tr '|' '\n') # Check for TODO items local todo_count todo_count=$(count_todo_items) local completed_percentage=$((found_sections * 100 / total_required)) # Reduce percentage based on TODO count (max reduction of 25%) if [ "$todo_count" -gt 0 ] 2>/dev/null; then local todo_penalty=$((todo_count * 2)) [ $todo_penalty -gt 25 ] && todo_penalty=25 completed_percentage=$((completed_percentage - todo_penalty)) fi [ $completed_percentage -lt 0 ] && completed_percentage=0 echo "$completed_percentage" } # Validate spec structure validate_spec() { local spec_type=$(determine_spec_type) local required_sections=$(get_required_sections "$spec_type") local content=$(cat "$SPEC_FILE") local pass_count=0 local warn_count=0 local error_count=0 local pass_items=() local warn_items=() local error_items=() # Check for proper formatting if grep -qE "^# " "$SPEC_FILE"; then pass_items+=("Title present") ((pass_count++)) else error_items+=("Missing title (# Title)") ((error_count++)) fi # Check required sections while IFS= read -r section; do if [ -z "$section" ]; then continue fi if grep -qE "^## $section" "$SPEC_FILE"; then pass_items+=("Section '$section' present") ((pass_count++)) else error_items+=("Missing required section: '$section'") ((error_count++)) fi done < <(echo "$required_sections" | tr '|' '\n') # Check for TODO items - only flag non-comment TODOs # TODOs inside HTML comments are just guidance and are acceptable local todo_count todo_count=$(grep -v "^ comments are just guidance templates and are acceptable local placeholder_count placeholder_count=$(grep -v "^