trax/scripts/validate_quality.sh

349 lines
10 KiB
Bash
Executable File

#!/bin/bash
# Quality Validation Script for Enhanced Workflow
# Enforces TDD, low-LOC, UTC timestamps, and code quality rules
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Configuration
MAX_LOC=300
MAX_LOC_JUSTIFIED=350
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
TASK_ID=""
FINAL_CHECK=false
# Help function
show_help() {
echo "Usage: $0 <task-id> [--final]"
echo ""
echo "Validates code quality before task completion:"
echo " - TDD compliance (tests written before code)"
echo " - LOC rules (files under 300 LOC, 350 max if justified)"
echo " - UTC timestamp compliance"
echo " - Code formatting and linting"
echo ""
echo "Options:"
echo " --final Perform final validation before task completion"
echo " --help Show this help message"
echo ""
echo "Examples:"
echo " $0 15 # Validate task 15"
echo " $0 15 --final # Final validation for task 15"
}
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--final)
FINAL_CHECK=true
shift
;;
--help)
show_help
exit 0
;;
-*)
echo -e "${RED}Error: Unknown option $1${NC}"
show_help
exit 1
;;
*)
if [[ -z "$TASK_ID" ]]; then
TASK_ID="$1"
else
echo -e "${RED}Error: Multiple task IDs specified${NC}"
exit 1
fi
shift
;;
esac
done
# Validate task ID
if [[ -z "$TASK_ID" ]]; then
echo -e "${RED}Error: Task ID is required${NC}"
show_help
exit 1
fi
echo -e "${BLUE}🔍 Quality Validation for Task $TASK_ID${NC}"
if [[ "$FINAL_CHECK" == "true" ]]; then
echo -e "${YELLOW}⚠️ Performing FINAL validation - task completion blocked if any checks fail${NC}"
fi
echo ""
# Function to check if a file is Python code
is_python_file() {
local file="$1"
[[ "$file" == *.py ]]
}
# Function to count lines of code (excluding comments and empty lines)
count_loc() {
local file="$1"
if [[ ! -f "$file" ]]; then
echo 0
return
fi
# Count non-comment, non-empty lines
grep -v '^\s*#' "$file" | grep -v '^\s*$' | wc -l
}
# Function to check UTC timestamp compliance
check_utc_timestamps() {
local file="$1"
local issues=()
if [[ ! -f "$file" ]]; then
return 0
fi
# Check for naive datetime usage
if grep -q "datetime\.now()" "$file"; then
issues+=("naive datetime.now() - should use datetime.now(timezone.utc)")
fi
# Check for deprecated utcnow
if grep -q "datetime\.utcnow()" "$file"; then
issues+=("deprecated datetime.utcnow() - should use datetime.now(timezone.utc)")
fi
# Check for time.time() usage in timing contexts
if grep -q "time\.time()" "$file"; then
issues+=("time.time() usage - consider using datetime for consistency")
fi
# Check for inconsistent filename formats
if grep -q "strftime.*%Y.*%m.*%d.*%H.*%M.*%S" "$file"; then
if ! grep -q "strftime.*%Y%m%d_%H%M%S" "$file"; then
issues+=("inconsistent filename timestamp format - should use YYYYMMDD_HHMMSS")
fi
fi
if [[ ${#issues[@]} -gt 0 ]]; then
echo -e "${RED}❌ UTC Timestamp Issues in $file:${NC}"
for issue in "${issues[@]}"; do
echo -e " ${RED}$issue${NC}"
done
return 1
fi
return 0
}
# Function to check LOC compliance
check_loc_compliance() {
local file="$1"
local loc
if [[ ! -f "$file" ]]; then
return 0
fi
loc=$(count_loc "$file")
if [[ $loc -gt $MAX_LOC_JUSTIFIED ]]; then
echo -e "${RED}❌ LOC Violation in $file: $loc lines (exceeds $MAX_LOC_JUSTIFIED limit)${NC}"
return 1
elif [[ $loc -gt $MAX_LOC ]]; then
echo -e "${YELLOW}⚠️ LOC Warning in $file: $loc lines (exceeds $MAX_LOC, but under $MAX_LOC_JUSTIFIED)${NC}"
echo -e " ${YELLOW} Consider splitting this file for better maintainability${NC}"
return 0 # Warning, not error
else
echo -e "${GREEN}✅ LOC OK: $file ($loc lines)${NC}"
return 0
fi
}
# Function to check test coverage
check_test_coverage() {
local task_id="$1"
local test_files=()
local source_files=()
# Find test files related to this task
while IFS= read -r -d '' file; do
if [[ "$file" == *"test"* ]] && [[ "$file" == *.py ]]; then
test_files+=("$file")
fi
done < <(find "$PROJECT_ROOT/tests" -name "*.py" -print0)
# Find source files that might need testing
while IFS= read -r -d '' file; do
if [[ "$file" == *.py ]] && [[ "$file" != *"__init__.py" ]]; then
source_files+=("$file")
fi
done < <(find "$PROJECT_ROOT/src" -name "*.py" -print0)
echo -e "${BLUE}📊 Test Coverage Analysis:${NC}"
if [[ ${#test_files[@]} -eq 0 ]]; then
echo -e "${YELLOW}⚠️ No test files found in tests/ directory${NC}"
return 1
fi
echo -e "${GREEN}✅ Found ${#test_files[@]} test files${NC}"
# Check if tests are passing
if command -v pytest >/dev/null 2>&1; then
echo -e "${BLUE}🧪 Running tests...${NC}"
if cd "$PROJECT_ROOT" && python -m pytest --tb=short -q; then
echo -e "${GREEN}✅ All tests passing${NC}"
else
echo -e "${RED}❌ Some tests are failing${NC}"
if [[ "$FINAL_CHECK" == "true" ]]; then
return 1
fi
fi
else
echo -e "${YELLOW}⚠️ pytest not available - skipping test execution${NC}"
fi
return 0
}
# Function to check code formatting
check_code_formatting() {
echo -e "${BLUE}🎨 Checking code formatting...${NC}"
if command -v black >/dev/null 2>&1; then
if cd "$PROJECT_ROOT" && python -m black --check --diff src/ tests/ 2>/dev/null; then
echo -e "${GREEN}✅ Code formatting OK${NC}"
else
echo -e "${RED}❌ Code formatting issues detected${NC}"
echo -e "${YELLOW} Run: uv run black src/ tests/ to fix${NC}"
if [[ "$FINAL_CHECK" == "true" ]]; then
return 1
fi
fi
else
echo -e "${YELLOW}⚠️ black not available - skipping formatting check${NC}"
fi
if command -v ruff >/dev/null 2>&1; then
if cd "$PROJECT_ROOT" && python -m ruff check src/ tests/ 2>/dev/null; then
echo -e "${GREEN}✅ Linting OK${NC}"
else
echo -e "${RED}❌ Linting issues detected${NC}"
echo -e "${YELLOW} Run: uv run ruff check --fix src/ tests/ to fix${NC}"
if [[ "$FINAL_CHECK" == "true" ]]; then
return 1
fi
fi
else
echo -e "${YELLOW}⚠️ ruff not available - skipping linting check${NC}"
fi
return 0
}
# Main validation function
main() {
local exit_code=0
local validation_results=()
echo -e "${BLUE}🚀 Starting Quality Validation...${NC}"
echo ""
# Check LOC compliance for all Python files
echo -e "${BLUE}📏 Checking LOC compliance...${NC}"
while IFS= read -r -d '' file; do
if is_python_file "$file"; then
if ! check_loc_compliance "$file"; then
validation_results+=("LOC_VIOLATION:$file")
if [[ "$FINAL_CHECK" == "true" ]]; then
exit_code=1
fi
fi
fi
done < <(find "$PROJECT_ROOT/src" -name "*.py" -print0)
echo ""
# Check UTC timestamp compliance
echo -e "${BLUE}⏰ Checking UTC timestamp compliance...${NC}"
while IFS= read -r -d '' file; do
if is_python_file "$file"; then
if ! check_utc_timestamps "$file"; then
validation_results+=("UTC_VIOLATION:$file")
if [[ "$FINAL_CHECK" == "true" ]]; then
exit_code=1
fi
fi
fi
done < <(find "$PROJECT_ROOT/src" -name "*.py" -print0)
echo ""
# Check test coverage
if ! check_test_coverage "$TASK_ID"; then
validation_results+=("TEST_VIOLATION")
if [[ "$FINAL_CHECK" == "true" ]]; then
exit_code=1
fi
fi
echo ""
# Check code formatting
if ! check_code_formatting; then
validation_results+=("FORMAT_VIOLATION")
if [[ "$FINAL_CHECK" == "true" ]]; then
exit_code=1
fi
fi
echo ""
# Summary
echo -e "${BLUE}📋 Validation Summary:${NC}"
if [[ ${#validation_results[@]} -eq 0 ]]; then
echo -e "${GREEN}🎉 All quality checks passed!${NC}"
echo -e "${GREEN}✅ Task $TASK_ID is ready for completion${NC}"
else
echo -e "${RED}❌ Quality issues found:${NC}"
for result in "${validation_results[@]}"; do
case "$result" in
LOC_VIOLATION:*)
echo -e " ${RED}• LOC violation in ${result#LOC_VIOLATION:}${NC}"
;;
UTC_VIOLATION:*)
echo -e " ${RED}• UTC timestamp violation in ${result#UTC_VIOLATION:}${NC}"
;;
TEST_VIOLATION)
echo -e " ${RED}• Test coverage or execution issues${NC}"
;;
FORMAT_VIOLATION)
echo -e " ${RED}• Code formatting or linting issues${NC}"
;;
esac
done
if [[ "$FINAL_CHECK" == "true" ]]; then
echo ""
echo -e "${RED}🚫 Task completion BLOCKED due to quality violations${NC}"
echo -e "${YELLOW} Fix all issues before marking task as complete${NC}"
else
echo ""
echo -e "${YELLOW}⚠️ Quality issues detected but not blocking progress${NC}"
echo -e "${YELLOW} Fix issues before final validation${NC}"
fi
fi
echo ""
echo -e "${BLUE}🔧 Quick Fix Commands:${NC}"
echo -e " ${BLUE}• Format code:${NC} uv run black src/ tests/"
echo -e " ${BLUE}• Fix linting:${NC} uv run ruff check --fix src/ tests/"
echo -e " ${BLUE}• Run tests:${NC} uv run pytest"
echo -e " ${BLUE}• Re-validate:${NC} $0 $TASK_ID"
exit $exit_code
}
# Run main function
main "$@"