349 lines
10 KiB
Bash
Executable File
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 "$@"
|