Last active
August 7, 2025 16:06
-
-
Save tosin2013/7b5d1140dbd23161321c9d73a2535ea0 to your computer and use it in GitHub Desktop.
aider-make.sh
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # aider-make: AI-powered Makefile automation with aider integration | |
| # Author: Generated for automation of make targets with AI assistance | |
| # Version: 1.0.0 | |
| set -euo pipefail # Exit on error, undefined vars, pipe failures | |
| # ============================================================================= | |
| # CONFIGURATION VARIABLES (User Configurable) | |
| # ============================================================================= | |
| # Default model to use with aider (can be overridden) | |
| DEFAULT_MODEL="${AIDER_MODEL:-deepseek}" | |
| # API key for the model (can be set via environment or command line) | |
| # Will be detected automatically based on model or prompted if needed | |
| DEFAULT_API_KEY="${AIDER_API_KEY:-}" | |
| # API base URL for OpenAI compatible endpoints | |
| DEFAULT_API_BASE="${AIDER_API_BASE:-${OPENAI_API_BASE:-}}" | |
| # Maximum number of fix attempts before giving up | |
| MAX_FIX_ATTEMPTS="${AIDER_MAKE_MAX_ATTEMPTS:-3}" | |
| # Extra arguments to pass to aider | |
| AIDER_EXTRA_ARGS="${AIDER_MAKE_EXTRA_ARGS:---yes --auto-commits --disable-playwright}" | |
| # Makefile name to look for | |
| MAKEFILE_NAME="Makefile" | |
| # Colors for output | |
| RED='\033[0;31m' | |
| GREEN='\033[0;32m' | |
| YELLOW='\033[1;33m' | |
| BLUE='\033[0;34m' | |
| PURPLE='\033[0;35m' | |
| CYAN='\033[0;36m' | |
| NC='\033[0m' # No Color | |
| # ============================================================================= | |
| # GLOBAL VARIABLES | |
| # ============================================================================= | |
| SCRIPT_NAME="$(basename "$0")" | |
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | |
| WORK_DIR="$(pwd)" | |
| LOG_DIR="${WORK_DIR}/.aider-make" | |
| LOG_FILE="${LOG_DIR}/aider-make.log" | |
| # Command line options | |
| MODEL="$DEFAULT_MODEL" | |
| API_KEY="$DEFAULT_API_KEY" | |
| API_BASE="$DEFAULT_API_BASE" | |
| TARGET="" | |
| LIST_TARGETS=false | |
| DRY_RUN=false | |
| ARCHITECT_MODE=false | |
| VERBOSE=false | |
| HELP=false | |
| # ============================================================================= | |
| # UTILITY FUNCTIONS | |
| # ============================================================================= | |
| # Print colored output | |
| print_color() { | |
| local color=$1 | |
| shift | |
| echo -e "${color}$*${NC}" | |
| } | |
| # Print info message | |
| info() { | |
| print_color "$BLUE" "[INFO] $*" | |
| } | |
| # Print success message | |
| success() { | |
| print_color "$GREEN" "[SUCCESS] $*" | |
| } | |
| # Print warning message | |
| warn() { | |
| print_color "$YELLOW" "[WARNING] $*" | |
| } | |
| # Print error message | |
| error() { | |
| print_color "$RED" "[ERROR] $*" | |
| } | |
| # Print debug message (only if verbose) | |
| debug() { | |
| if [[ "$VERBOSE" == true ]]; then | |
| print_color "$PURPLE" "[DEBUG] $*" | |
| fi | |
| } | |
| # Log message to file | |
| log() { | |
| local timestamp=$(date '+%Y-%m-%d %H:%M:%S') | |
| echo "[$timestamp] $*" >> "$LOG_FILE" | |
| } | |
| # Setup logging directory | |
| setup_logging() { | |
| mkdir -p "$LOG_DIR" | |
| touch "$LOG_FILE" | |
| log "=== aider-make session started ===" | |
| log "Working directory: $WORK_DIR" | |
| log "Model: $MODEL" | |
| } | |
| # ============================================================================= | |
| # HELP AND USAGE | |
| # ============================================================================= | |
| show_help() { | |
| cat << EOF | |
| $SCRIPT_NAME - AI-powered Makefile automation with aider integration | |
| USAGE: | |
| $SCRIPT_NAME [OPTIONS] [TARGET] | |
| DESCRIPTION: | |
| This script integrates aider CLI with Makefile automation to provide | |
| AI-powered assistance for running make targets and automatically fixing | |
| failing tests or build issues. | |
| OPTIONS: | |
| -h, --help Show this help message | |
| -l, --list List available make targets | |
| -m, --model MODEL Specify AI model to use (default: $DEFAULT_MODEL) | |
| -k, --api-key KEY Specify API key for the model | |
| -b, --api-base URL Specify API base URL for OpenAI compatible endpoints | |
| -a, --architect Start aider in architect mode | |
| -d, --dry-run Show what would be done without executing | |
| -v, --verbose Enable verbose output | |
| --max-attempts N Maximum fix attempts (default: $MAX_FIX_ATTEMPTS) | |
| EXAMPLES: | |
| $SCRIPT_NAME # Interactive mode - show targets and let user choose | |
| $SCRIPT_NAME test # Run 'make test' with AI assistance | |
| $SCRIPT_NAME --list # List all available make targets | |
| $SCRIPT_NAME --model gpt-4 test # Use GPT-4 model for assistance | |
| $SCRIPT_NAME --dry-run build # Show what would happen without executing | |
| # OpenAI compatible API examples: | |
| $SCRIPT_NAME --model gpt-4 --api-base http://localhost:1234/v1 --api-key sk-xxx test | |
| $SCRIPT_NAME --model llama3 --api-base https://api.together.xyz/v1 test | |
| ENVIRONMENT VARIABLES: | |
| AIDER_MODEL # Default model to use | |
| AIDER_API_KEY # Generic API key (fallback) | |
| AIDER_API_BASE # API base URL for OpenAI compatible endpoints | |
| # Provider-specific API keys (recommended): | |
| OPENAI_API_KEY # For OpenAI models (gpt-*, o1-*, o3-*) | |
| OPENAI_API_BASE # For OpenAI compatible endpoints | |
| ANTHROPIC_API_KEY # For Anthropic models (claude-*, sonnet*) | |
| GEMINI_API_KEY # For Google Gemini models | |
| DEEPSEEK_API_KEY # For DeepSeek models | |
| AIDER_MAKE_MAX_ATTEMPTS # Maximum fix attempts | |
| AIDER_MAKE_EXTRA_ARGS # Extra arguments for aider | |
| CONFIGURATION: | |
| You can also create a .aider-make.conf file in your project root with: | |
| MODEL=deepseek | |
| API_KEY=your_key_here | |
| API_BASE=https://api.openai.com/v1 | |
| MAX_ATTEMPTS=3 | |
| For different providers, use the appropriate environment variables: | |
| export OPENAI_API_KEY=your_openai_key # For OpenAI models | |
| export ANTHROPIC_API_KEY=your_anthropic_key # For Claude models | |
| export GEMINI_API_KEY=your_gemini_key # For Gemini models | |
| export DEEPSEEK_API_KEY=your_deepseek_key # For DeepSeek models | |
| export OPENAI_API_BASE=https://your-endpoint.com/v1 # For custom endpoints | |
| EOF | |
| } | |
| # ============================================================================= | |
| # API KEY MANAGEMENT | |
| # ============================================================================= | |
| prompt_for_api_key() { | |
| local suggested_env_var="" | |
| local provider_name="" | |
| # Suggest appropriate environment variable based on model | |
| case "$MODEL" in | |
| gpt-*|o1-*|o3-*) | |
| suggested_env_var="OPENAI_API_KEY" | |
| provider_name="OpenAI" | |
| ;; | |
| claude-*|sonnet*) | |
| suggested_env_var="ANTHROPIC_API_KEY" | |
| provider_name="Anthropic" | |
| ;; | |
| gemini*) | |
| suggested_env_var="GEMINI_API_KEY" | |
| provider_name="Google Gemini" | |
| ;; | |
| deepseek*) | |
| suggested_env_var="DEEPSEEK_API_KEY" | |
| provider_name="DeepSeek" | |
| ;; | |
| *) | |
| # Try to infer from model name | |
| local provider=$(echo "$MODEL" | cut -d'/' -f1) | |
| if [[ "$provider" != "$MODEL" ]]; then | |
| suggested_env_var="${provider^^}_API_KEY" | |
| provider_name="$provider" | |
| else | |
| suggested_env_var="OPENAI_API_KEY" | |
| provider_name="OpenAI-compatible" | |
| fi | |
| ;; | |
| esac | |
| warn "No API key found for model '$MODEL'" | |
| info "You can either:" | |
| info " 1. Set the environment variable: export $suggested_env_var=your_key_here" | |
| info " 2. Use the --api-key option: --api-key your_key_here" | |
| info " 3. Enter the API key now (will not be saved)" | |
| echo | |
| # In dry-run mode, don't prompt | |
| if [[ "$DRY_RUN" == true ]]; then | |
| warn "DRY RUN: Would prompt for API key" | |
| return 0 | |
| fi | |
| # Prompt for API key | |
| echo -n "Enter your $provider_name API key (or 'q' to quit): " | |
| read -r -s user_api_key | |
| echo # New line after hidden input | |
| if [[ "$user_api_key" == "q" || "$user_api_key" == "quit" ]]; then | |
| info "Exiting..." | |
| exit 0 | |
| fi | |
| if [[ -z "$user_api_key" ]]; then | |
| error "No API key provided. Cannot continue." | |
| exit 1 | |
| fi | |
| API_KEY="$user_api_key" | |
| success "API key accepted" | |
| # Suggest setting environment variable for future use | |
| info "For future use, consider setting: export $suggested_env_var=your_key_here" | |
| } | |
| # ============================================================================= | |
| # DEPENDENCY CHECKING | |
| # ============================================================================= | |
| check_dependencies() { | |
| info "Checking dependencies..." | |
| # Check if we're in a directory with a Makefile | |
| if [[ ! -f "$MAKEFILE_NAME" && ! -f "makefile" && ! -f "GNUmakefile" ]]; then | |
| error "No Makefile found in current directory" | |
| error "Please run this script from a directory containing a Makefile" | |
| return 1 | |
| fi | |
| # Determine which Makefile to use | |
| if [[ -f "GNUmakefile" ]]; then | |
| MAKEFILE_NAME="GNUmakefile" | |
| elif [[ -f "$MAKEFILE_NAME" ]]; then | |
| MAKEFILE_NAME="Makefile" | |
| else | |
| MAKEFILE_NAME="makefile" | |
| fi | |
| debug "Using Makefile: $MAKEFILE_NAME" | |
| # Check if make is available | |
| if ! command -v make &> /dev/null; then | |
| error "make command not found. Please install make." | |
| return 1 | |
| fi | |
| # Check if aider is available | |
| if ! command -v aider &> /dev/null; then | |
| warn "aider not found. Attempting to install..." | |
| if ! install_aider; then | |
| error "Failed to install aider. Please install manually:" | |
| error " python -m pip install aider-install" | |
| error " aider-install" | |
| return 1 | |
| fi | |
| fi | |
| # Validate API key if model requires it | |
| if [[ -z "$API_KEY" ]]; then | |
| # Check if we have provider-specific environment variables | |
| local found_env_key="" | |
| case "$MODEL" in | |
| gpt-*|o1-*|o3-*) | |
| [[ -n "${OPENAI_API_KEY:-}" ]] && found_env_key="OPENAI_API_KEY" | |
| ;; | |
| claude-*|sonnet*) | |
| [[ -n "${ANTHROPIC_API_KEY:-}" ]] && found_env_key="ANTHROPIC_API_KEY" | |
| ;; | |
| gemini*) | |
| [[ -n "${GEMINI_API_KEY:-}" ]] && found_env_key="GEMINI_API_KEY" | |
| ;; | |
| deepseek*) | |
| [[ -n "${DEEPSEEK_API_KEY:-}" ]] && found_env_key="DEEPSEEK_API_KEY" | |
| ;; | |
| *) | |
| # Try to find any common API key environment variables | |
| if [[ -n "${OPENAI_API_KEY:-}" ]]; then | |
| found_env_key="OPENAI_API_KEY" | |
| elif [[ -n "${ANTHROPIC_API_KEY:-}" ]]; then | |
| found_env_key="ANTHROPIC_API_KEY" | |
| elif [[ -n "${GEMINI_API_KEY:-}" ]]; then | |
| found_env_key="GEMINI_API_KEY" | |
| elif [[ -n "${DEEPSEEK_API_KEY:-}" ]]; then | |
| found_env_key="DEEPSEEK_API_KEY" | |
| fi | |
| ;; | |
| esac | |
| # If we found an environment variable, use it | |
| if [[ -n "$found_env_key" ]]; then | |
| API_KEY="${!found_env_key}" | |
| debug "Using API key from $found_env_key environment variable" | |
| else | |
| # No API key found, prompt user for it | |
| prompt_for_api_key | |
| fi | |
| fi | |
| # Log API configuration for debugging | |
| if [[ -n "$API_BASE" ]]; then | |
| debug "Using custom API base: $API_BASE" | |
| log "API base configured: $API_BASE" | |
| fi | |
| success "Dependencies check completed" | |
| return 0 | |
| } | |
| # Install aider if not present | |
| install_aider() { | |
| info "Installing aider..." | |
| # Check if python is available | |
| if ! command -v python3 &> /dev/null && ! command -v python &> /dev/null; then | |
| error "Python not found. Please install Python first." | |
| return 1 | |
| fi | |
| # Try to install aider | |
| local python_cmd="python3" | |
| if ! command -v python3 &> /dev/null; then | |
| python_cmd="python" | |
| fi | |
| if $python_cmd -m pip install aider-install; then | |
| if aider-install; then | |
| success "aider installed successfully" | |
| return 0 | |
| else | |
| error "aider-install failed" | |
| return 1 | |
| fi | |
| else | |
| error "Failed to install aider-install package" | |
| return 1 | |
| fi | |
| } | |
| # ============================================================================= | |
| # MAKEFILE PARSING | |
| # ============================================================================= | |
| parse_makefile_targets() { | |
| info "Parsing Makefile targets..." | |
| local targets=() | |
| local makefile="$MAKEFILE_NAME" | |
| # Extract targets using regex pattern | |
| # This pattern matches lines that start with target names followed by colon | |
| while IFS= read -r line; do | |
| # Skip comments and empty lines | |
| [[ "$line" =~ ^[[:space:]]*# ]] && continue | |
| [[ -z "${line// }" ]] && continue | |
| # Extract target names (handle multiple targets on one line) | |
| if [[ "$line" =~ ^([a-zA-Z0-9_.-]+[[:space:]]*)+: ]]; then | |
| local target_line="${line%%:*}" | |
| # Split multiple targets | |
| IFS=' ' read -ra target_array <<< "$target_line" | |
| for target in "${target_array[@]}"; do | |
| # Skip internal targets and variables | |
| if [[ ! "$target" =~ ^\. ]] && [[ ! "$target" =~ = ]]; then | |
| targets+=("$target") | |
| fi | |
| done | |
| fi | |
| done < "$makefile" | |
| # Handle included makefiles | |
| while IFS= read -r include_line; do | |
| if [[ "$include_line" =~ ^include[[:space:]]+(.+)$ ]]; then | |
| local included_file="${BASH_REMATCH[1]}" | |
| # Expand environment variables in the path | |
| included_file=$(eval echo "$included_file") | |
| if [[ -f "$included_file" ]]; then | |
| debug "Processing included makefile: $included_file" | |
| while IFS= read -r line; do | |
| [[ "$line" =~ ^[[:space:]]*# ]] && continue | |
| [[ -z "${line// }" ]] && continue | |
| if [[ "$line" =~ ^([a-zA-Z0-9_.-]+[[:space:]]*)+: ]]; then | |
| local target_line="${line%%:*}" | |
| IFS=' ' read -ra target_array <<< "$target_line" | |
| for target in "${target_array[@]}"; do | |
| if [[ ! "$target" =~ ^\. ]] && [[ ! "$target" =~ = ]]; then | |
| targets+=("$target") | |
| fi | |
| done | |
| fi | |
| done < "$included_file" | |
| fi | |
| fi | |
| done < "$makefile" | |
| # Remove duplicates and sort | |
| printf '%s\n' "${targets[@]}" | sort -u | |
| } | |
| # ============================================================================= | |
| # TARGET DISPLAY AND SELECTION | |
| # ============================================================================= | |
| display_targets() { | |
| local targets=("$@") | |
| if [[ ${#targets[@]} -eq 0 ]]; then | |
| warn "No targets found in Makefile" | |
| return 1 | |
| fi | |
| print_color "$CYAN" "\nAvailable make targets:" | |
| print_color "$CYAN" "=======================" | |
| for i in "${!targets[@]}"; do | |
| printf "%3d) %s\n" $((i + 1)) "${targets[i]}" | |
| done | |
| echo | |
| } | |
| select_target() { | |
| local targets=("$@") | |
| while true; do | |
| echo -n "Select target (number or name, 'q' to quit): " | |
| read -r selection | |
| # Handle quit | |
| if [[ "$selection" == "q" || "$selection" == "quit" ]]; then | |
| info "Exiting..." | |
| exit 0 | |
| fi | |
| # Handle numeric selection | |
| if [[ "$selection" =~ ^[0-9]+$ ]]; then | |
| local index=$((selection - 1)) | |
| if [[ $index -ge 0 && $index -lt ${#targets[@]} ]]; then | |
| echo "${targets[index]}" | |
| return 0 | |
| else | |
| error "Invalid selection. Please choose 1-${#targets[@]}" | |
| continue | |
| fi | |
| fi | |
| # Handle name selection | |
| for target in "${targets[@]}"; do | |
| if [[ "$target" == "$selection" ]]; then | |
| echo "$target" | |
| return 0 | |
| fi | |
| done | |
| error "Target '$selection' not found. Please try again." | |
| done | |
| } | |
| # ============================================================================= | |
| # MAKE EXECUTION | |
| # ============================================================================= | |
| run_make_target() { | |
| local target="$1" | |
| local output_file="${LOG_DIR}/make_output_${target}_$(date +%s).log" | |
| info "Running 'make $target'..." | |
| log "Executing: make $target" | |
| log "Output will be saved to: $output_file" | |
| if [[ "$DRY_RUN" == true ]]; then | |
| info "DRY RUN: Would execute 'make $target'" | |
| return 0 | |
| fi | |
| local start_time=$(date +%s) | |
| local exit_code=0 | |
| # Run make and capture output | |
| if make "$target" 2>&1 | tee "$output_file"; then | |
| exit_code=0 | |
| local end_time=$(date +%s) | |
| local duration=$((end_time - start_time)) | |
| success "make $target completed successfully in ${duration}s" | |
| log "make $target completed successfully in ${duration}s" | |
| else | |
| exit_code=$? | |
| local end_time=$(date +%s) | |
| local duration=$((end_time - start_time)) | |
| error "make $target failed with exit code $exit_code after ${duration}s" | |
| log "make $target failed with exit code $exit_code after ${duration}s" | |
| log "Output file for analysis: $output_file" | |
| # Store the output file path for analysis | |
| echo "$output_file" | |
| fi | |
| return $exit_code | |
| } | |
| # ============================================================================= | |
| # FAILURE ANALYSIS | |
| # ============================================================================= | |
| analyze_failure() { | |
| local output_file="$1" | |
| local target="$2" | |
| info "Analyzing failure output from: $output_file" | |
| if [[ ! -f "$output_file" ]]; then | |
| error "Output file not found: $output_file" | |
| return 1 | |
| fi | |
| local file_size=$(wc -l < "$output_file") | |
| debug "Output file contains $file_size lines" | |
| # Extract relevant error information | |
| local error_summary="" | |
| local error_context="" | |
| # Look for common error patterns with more comprehensive extraction | |
| if grep -q "error:" "$output_file"; then | |
| error_context=$(grep -A 5 -B 2 "error:" "$output_file" | head -30) | |
| debug "Found 'error:' pattern" | |
| elif grep -q "Error:" "$output_file"; then | |
| error_context=$(grep -A 5 -B 2 "Error:" "$output_file" | head -30) | |
| debug "Found 'Error:' pattern" | |
| elif grep -q "FAILED" "$output_file"; then | |
| error_context=$(grep -A 5 -B 2 "FAILED" "$output_file" | head -30) | |
| debug "Found 'FAILED' pattern" | |
| elif grep -q "FAIL" "$output_file"; then | |
| error_context=$(grep -A 5 -B 2 "FAIL" "$output_file" | head -30) | |
| debug "Found 'FAIL' pattern" | |
| elif grep -q "AssertionError\|Exception\|Traceback" "$output_file"; then | |
| error_context=$(grep -A 10 -B 2 "AssertionError\|Exception\|Traceback" "$output_file" | head -40) | |
| debug "Found Python exception pattern" | |
| else | |
| # Get last 30 lines as context for unknown failures | |
| error_context=$(tail -30 "$output_file") | |
| debug "Using last 30 lines as error context" | |
| fi | |
| # Determine failure type | |
| local failure_type="unknown" | |
| if grep -q "test.*fail\|FAIL\|FAILED\|AssertionError" "$output_file"; then | |
| failure_type="test" | |
| elif grep -q "compile\|compilation\|syntax" "$output_file"; then | |
| failure_type="compilation" | |
| elif grep -q "lint\|style\|format" "$output_file"; then | |
| failure_type="linting" | |
| elif grep -q "missing\|not found\|No such file" "$output_file"; then | |
| failure_type="dependency" | |
| elif grep -q "ImportError\|ModuleNotFoundError" "$output_file"; then | |
| failure_type="import" | |
| fi | |
| info "Failure type detected: $failure_type" | |
| log "Failure analysis - Type: $failure_type" | |
| log "Error context length: $(echo "$error_context" | wc -l) lines" | |
| # Save error context to a separate file for debugging | |
| local error_context_file="${LOG_DIR}/error_context_${target}_$(date +%s).log" | |
| echo "$error_context" > "$error_context_file" | |
| debug "Error context saved to: $error_context_file" | |
| # Return the error context for aider | |
| echo "$error_context" | |
| } | |
| # ============================================================================= | |
| # AIDER INTEGRATION | |
| # ============================================================================= | |
| fix_with_aider() { | |
| local target="$1" | |
| local error_context="$2" | |
| local attempt="$3" | |
| info "Attempting to fix issues with aider (attempt $attempt/$MAX_FIX_ATTEMPTS)..." | |
| if [[ "$DRY_RUN" == true ]]; then | |
| info "DRY RUN: Would run aider to fix issues" | |
| return 0 | |
| fi | |
| # Create a temporary file for the error context to avoid command line issues | |
| local temp_message_file="${LOG_DIR}/aider_message_${target}_${attempt}_$(date +%s).txt" | |
| # Construct the message for aider and write to temp file | |
| cat > "$temp_message_file" << EOF | |
| The make target '$target' failed with the following output: | |
| $error_context | |
| Please analyze the failure and fix the underlying issues in the code. Focus on: | |
| 1. Understanding the root cause of the failure | |
| 2. Making the minimal necessary changes to fix the issue | |
| 3. Ensuring the fix doesn't break other functionality | |
| 4. Following best practices for the detected programming language/framework | |
| If this is a test failure, fix the code being tested rather than changing the test unless the test itself is clearly wrong. | |
| EOF | |
| # Construct aider command | |
| local aider_cmd="aider" | |
| # Add model specification | |
| if [[ -n "$MODEL" ]]; then | |
| aider_cmd="$aider_cmd --model $MODEL" | |
| fi | |
| # Set up environment variables for API configuration | |
| local env_vars=() | |
| # Add API base URL if provided (for OpenAI compatible endpoints) | |
| if [[ -n "$API_BASE" ]]; then | |
| env_vars+=("OPENAI_API_BASE=$API_BASE") | |
| fi | |
| # Set API key environment variable based on model/provider | |
| if [[ -n "$API_KEY" ]]; then | |
| # If API_BASE is set, assume OpenAI compatible API | |
| if [[ -n "$API_BASE" ]]; then | |
| env_vars+=("OPENAI_API_KEY=$API_KEY") | |
| else | |
| # Set provider-specific environment variables | |
| case "$MODEL" in | |
| gpt-*|o1-*|o3-*) | |
| env_vars+=("OPENAI_API_KEY=$API_KEY") | |
| ;; | |
| claude-*|sonnet*) | |
| env_vars+=("ANTHROPIC_API_KEY=$API_KEY") | |
| ;; | |
| gemini*) | |
| env_vars+=("GEMINI_API_KEY=$API_KEY") | |
| ;; | |
| deepseek*) | |
| env_vars+=("DEEPSEEK_API_KEY=$API_KEY") | |
| ;; | |
| *) | |
| # For unknown models, try to infer from model name | |
| local provider=$(echo "$MODEL" | cut -d'/' -f1 | tr '[:lower:]' '[:upper:]') | |
| if [[ "$provider" != "$MODEL" ]]; then | |
| env_vars+=("${provider}_API_KEY=$API_KEY") | |
| else | |
| # Default to OpenAI format | |
| env_vars+=("OPENAI_API_KEY=$API_KEY") | |
| fi | |
| ;; | |
| esac | |
| fi | |
| fi | |
| # Add environment variables to the command using --set-env | |
| for env_var in "${env_vars[@]}"; do | |
| aider_cmd="$aider_cmd --set-env $env_var" | |
| done | |
| # Add architect mode if requested | |
| if [[ "$ARCHITECT_MODE" == true ]]; then | |
| aider_cmd="$aider_cmd --architect" | |
| fi | |
| # Add extra arguments | |
| aider_cmd="$aider_cmd $AIDER_EXTRA_ARGS" | |
| # Use --message-file instead of --message to avoid command line parsing issues | |
| aider_cmd="$aider_cmd --message-file \"$temp_message_file\"" | |
| debug "Executing: $aider_cmd" | |
| debug "Message file: $temp_message_file" | |
| log "Aider command: $aider_cmd" | |
| log "Message file: $temp_message_file" | |
| # Execute aider and capture output | |
| local aider_output_file="${LOG_DIR}/aider_output_${target}_${attempt}_$(date +%s).log" | |
| local aider_exit_code=0 | |
| info "Running aider (output logged to: $aider_output_file)..." | |
| if eval "$aider_cmd" 2>&1 | tee "$aider_output_file"; then | |
| success "aider completed successfully" | |
| log "aider fix attempt $attempt completed successfully" | |
| aider_exit_code=0 | |
| else | |
| aider_exit_code=$? | |
| error "aider failed with exit code $aider_exit_code" | |
| log "aider fix attempt $attempt failed with exit code $aider_exit_code" | |
| log "aider output saved to: $aider_output_file" | |
| fi | |
| # Clean up temporary message file | |
| rm -f "$temp_message_file" | |
| return $aider_exit_code | |
| } | |
| # ============================================================================= | |
| # MAIN WORKFLOW | |
| # ============================================================================= | |
| main_workflow() { | |
| local target="$1" | |
| # If no target specified, show interactive menu | |
| if [[ -z "$target" ]]; then | |
| local targets_array | |
| mapfile -t targets_array < <(parse_makefile_targets) | |
| if [[ ${#targets_array[@]} -eq 0 ]]; then | |
| error "No targets found in Makefile" | |
| return 1 | |
| fi | |
| display_targets "${targets_array[@]}" | |
| target=$(select_target "${targets_array[@]}") | |
| fi | |
| info "Selected target: $target" | |
| log "Selected target: $target" | |
| # Run the make target | |
| local output_file | |
| if output_file=$(run_make_target "$target"); then | |
| # Success - we're done | |
| success "Target '$target' completed successfully!" | |
| return 0 | |
| else | |
| # Failure - try to fix with aider | |
| warn "Target '$target' failed. Attempting to fix with AI assistance..." | |
| local attempt=1 | |
| while [[ $attempt -le $MAX_FIX_ATTEMPTS ]]; do | |
| # Analyze the failure | |
| local error_context | |
| error_context=$(analyze_failure "$output_file" "$target") | |
| if [[ -z "$error_context" ]]; then | |
| error "Could not extract error context from output" | |
| return 1 | |
| fi | |
| # Try to fix with aider | |
| if fix_with_aider "$target" "$error_context" "$attempt"; then | |
| info "Fix applied. Retrying make target..." | |
| # Retry the make target | |
| if output_file=$(run_make_target "$target"); then | |
| success "Target '$target' now passes after AI fix!" | |
| success "Fixed in $attempt attempt(s)" | |
| return 0 | |
| else | |
| warn "Target still failing after fix attempt $attempt" | |
| ((attempt++)) | |
| fi | |
| else | |
| error "aider fix attempt $attempt failed" | |
| ((attempt++)) | |
| fi | |
| done | |
| error "Failed to fix target '$target' after $MAX_FIX_ATTEMPTS attempts" | |
| error "You may need to manually investigate the issue" | |
| return 1 | |
| fi | |
| } | |
| # ============================================================================= | |
| # COMMAND LINE PARSING | |
| # ============================================================================= | |
| parse_arguments() { | |
| while [[ $# -gt 0 ]]; do | |
| case $1 in | |
| -h|--help) | |
| HELP=true | |
| shift | |
| ;; | |
| -l|--list) | |
| LIST_TARGETS=true | |
| shift | |
| ;; | |
| -m|--model) | |
| MODEL="$2" | |
| shift 2 | |
| ;; | |
| -k|--api-key) | |
| API_KEY="$2" | |
| shift 2 | |
| ;; | |
| -b|--api-base) | |
| API_BASE="$2" | |
| shift 2 | |
| ;; | |
| -a|--architect) | |
| ARCHITECT_MODE=true | |
| shift | |
| ;; | |
| -d|--dry-run) | |
| DRY_RUN=true | |
| shift | |
| ;; | |
| -v|--verbose) | |
| VERBOSE=true | |
| shift | |
| ;; | |
| --max-attempts) | |
| MAX_FIX_ATTEMPTS="$2" | |
| shift 2 | |
| ;; | |
| -*) | |
| error "Unknown option: $1" | |
| show_help | |
| exit 1 | |
| ;; | |
| *) | |
| if [[ -z "$TARGET" ]]; then | |
| TARGET="$1" | |
| else | |
| error "Multiple targets specified: '$TARGET' and '$1'" | |
| exit 1 | |
| fi | |
| shift | |
| ;; | |
| esac | |
| done | |
| } | |
| # ============================================================================= | |
| # CONFIGURATION FILE LOADING | |
| # ============================================================================= | |
| load_config_file() { | |
| local config_file=".aider-make.conf" | |
| if [[ -f "$config_file" ]]; then | |
| info "Loading configuration from $config_file" | |
| # Source the config file safely | |
| while IFS='=' read -r key value; do | |
| # Skip comments and empty lines | |
| [[ "$key" =~ ^[[:space:]]*# ]] && continue | |
| [[ -z "${key// }" ]] && continue | |
| # Remove leading/trailing whitespace | |
| key=$(echo "$key" | xargs) | |
| value=$(echo "$value" | xargs) | |
| # Set variables if not already set by command line or environment | |
| case "$key" in | |
| MODEL) | |
| [[ "$MODEL" == "$DEFAULT_MODEL" ]] && MODEL="$value" | |
| ;; | |
| API_KEY) | |
| [[ "$API_KEY" == "$DEFAULT_API_KEY" ]] && API_KEY="$value" | |
| ;; | |
| API_BASE) | |
| [[ "$API_BASE" == "$DEFAULT_API_BASE" ]] && API_BASE="$value" | |
| ;; | |
| MAX_ATTEMPTS) | |
| [[ "$MAX_FIX_ATTEMPTS" == "${AIDER_MAKE_MAX_ATTEMPTS:-3}" ]] && MAX_FIX_ATTEMPTS="$value" | |
| ;; | |
| esac | |
| done < "$config_file" | |
| debug "Configuration loaded from $config_file" | |
| fi | |
| } | |
| # ============================================================================= | |
| # MAIN FUNCTION | |
| # ============================================================================= | |
| main() { | |
| # Parse command line arguments | |
| parse_arguments "$@" | |
| # Load configuration file | |
| load_config_file | |
| # Show help if requested | |
| if [[ "$HELP" == true ]]; then | |
| show_help | |
| exit 0 | |
| fi | |
| # Setup logging | |
| setup_logging | |
| # Check dependencies | |
| if ! check_dependencies; then | |
| exit 1 | |
| fi | |
| # List targets if requested | |
| if [[ "$LIST_TARGETS" == true ]]; then | |
| info "Available make targets:" | |
| parse_makefile_targets | |
| exit 0 | |
| fi | |
| # Run main workflow | |
| if main_workflow "$TARGET"; then | |
| success "aider-make completed successfully!" | |
| exit 0 | |
| else | |
| error "aider-make failed" | |
| exit 1 | |
| fi | |
| } | |
| # ============================================================================= | |
| # SCRIPT ENTRY POINT | |
| # ============================================================================= | |
| # Only run main if script is executed directly (not sourced) | |
| if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then | |
| main "$@" | |
| fi | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.