DEV Community

Yigit Konur
Yigit Konur

Posted on

Complete Guide: How to Set AI Coding Rules for Aider

Aider takes a conventions-first approach: separate human-readable rules files from machine-readable configuration. This philosophy means:

  • .aider.conf.yml: Controls Aider's behavior (models, git, linting, file handling)
  • CONVENTIONS.md: Contains your coding standards, patterns, and project context
  • Hierarchical loading: Configuration merges from global → project → command-line
  • YAML format: Human-readable, comment-friendly, version-controllable

The Aider Philosophy

Unlike tools that embed rules in JSON or require special formats, Aider embraces external documentation. Your CONVENTIONS.md is:

  • Plain Markdown (no special syntax required)
  • Cached by Anthropic's prompt caching (cost-efficient)
  • Shared with your team via git
  • Usable by humans and AI alike

Configuration Hierarchy

Priority (Lowest to Highest):
1. ~/.aider.conf.yml          (Global user defaults)
2. Parent directories         (Recursively searched)
3. ./.aider.conf.yml          (Project root)
4. ./subdir/.aider.conf.yml   (Current directory)
5. --config <file>            (Explicit config file)
6. Environment variables      (AIDER_MODEL, etc.)
7. Command-line flags         (--model, --auto-commits, etc.)
Enter fullscreen mode Exit fullscreen mode

Key Insight: Later configurations override earlier ones, allowing project-specific customization while maintaining global defaults.


2. File Locations and Hierarchy {#2-file-locations}

Primary Configuration File

.aider.conf.yml

Location Options:

~/.aider.conf.yml              # Global (all projects)
/path/to/project/.aider.conf.yml   # Project-specific
/path/to/project/subdir/.aider.conf.yml  # Subdirectory override
Enter fullscreen mode Exit fullscreen mode

Search Behavior:

  • Aider starts in current working directory
  • Searches up the directory tree to filesystem root
  • Loads and merges all found configs (not just first)
  • Later configs override earlier settings

Conventions and Rules Files

Recommended Locations:

CONVENTIONS.md                 # Project root (most common)
docs/CONVENTIONS.md            # Alternative
.aider/CONVENTIONS.md          # Hidden directory
CODING_STANDARDS.md            # Alternative naming
Enter fullscreen mode Exit fullscreen mode

Multiple Files Supported:

read:
  - CONVENTIONS.md
  - docs/architecture.md
  - .editorconfig
  - docs/api-design-principles.md
Enter fullscreen mode Exit fullscreen mode

Aider Internal Files

Automatically Generated (typically in project root):

.aider.input.history           # Command history (readline)
.aider.chat.history.md         # Conversation transcript (Markdown)
.aider.llm.history             # Full LLM request/response logs
Enter fullscreen mode Exit fullscreen mode

Add to .gitignore:

# Aider history files (user-specific, don't commit)
.aider.input.history
.aider.chat.history.md
.aider.llm.history
Enter fullscreen mode Exit fullscreen mode

Exception: Keep .aider.conf.yml and CONVENTIONS.md in version control.

Environment Variables

Aider respects these environment variables:

# API Keys
export OPENAI_API_KEY=sk-...
export ANTHROPIC_API_KEY=sk-ant-...
export GEMINI_API_KEY=...

# Aider-specific (prefix with AIDER_)
export AIDER_MODEL=claude-3-5-sonnet-20241022
export AIDER_DARK_MODE=true
export AIDER_AUTO_COMMITS=false
export AIDER_ANALYTICS_DISABLE=true

# Useful for organization-wide defaults
export AIDER_WEAK_MODEL=gpt-4o-mini
Enter fullscreen mode Exit fullscreen mode

Precedence: Environment variables override config files but are overridden by command-line flags.


3. The .aider.conf.yml Complete Reference {#3-aider-conf-yml}

Anatomy of a Configuration File

#############################################
# .aider.conf.yml - Complete Reference
# Location: Project root or ~/.aider.conf.yml
#############################################

####################
# MODEL SELECTION
####################

# Primary model for code generation
model: claude-3-5-sonnet-20241022

# Cheaper model for summaries and commit messages
weak-model: gpt-4o-mini

# Editor model for architect mode (optional)
editor-model: claude-3-5-sonnet-20241022

# Model aliases for quick switching
alias:
  - "sonnet:claude-3-5-sonnet-20241022"
  - "opus:claude-3-opus-20240229"
  - "gpt4:gpt-4o"
  - "fast:gpt-4o-mini"
  - "local:ollama/qwen2.5-coder"

####################
# EDIT BEHAVIOR
####################

# How AI returns code changes
# Options: diff, diff-fenced, udiff, whole, editor-diff, editor-whole
edit-format: diff

# Architect mode (two-step editing with separate models)
architect: false
auto-accept-architect: true
editor-edit-format: editor-diff

####################
# RULES & CONTEXT
####################

# Short inline rules (use sparingly)
rules:
  - "Never commit secrets or API keys"
  - "Always run linter before committing"

# External conventions files (RECOMMENDED)
read:
  - CONVENTIONS.md
  - docs/architecture.md
  - .editorconfig

# Files always included in context
file:
  - package.json
  - tsconfig.json
  - src/types/global.d.ts

####################
# REPOSITORY MAP
####################

# Tokens allocated to repo map (0=disable, 1024=default)
map-tokens: 1024

# When to refresh repo map
# Options: auto (default), always, files, manual
map-refresh: auto

# Token multiplier when no files specified
map-multiplier-no-files: 2

####################
# GIT INTEGRATION
####################

# Auto-commit AI changes
auto-commits: false

# Allow commits when repo is dirty
dirty-commits: true

# Attribution settings
attribute-author: true
attribute-committer: true
attribute-commit-message-author: false
attribute-commit-message-committer: false
attribute-co-authored-by: true  # Preferred method

# Skip git hooks (e.g., pre-commit)
git-commit-verify: false

# Custom commit message prompt
commit-prompt: "Write a concise commit message following Conventional Commits format"

# Commit message language
commit-language: en

####################
# LINTING & TESTING
####################

# Auto-lint after changes
auto-lint: true

# Lint commands by file type
lint-cmd:
  - "python: ruff check --fix --select I,F,E,W"
  - "javascript: eslint --fix"
  - "typescript: eslint --fix"

# Auto-run tests after changes
auto-test: false

# Test command
test-cmd: "pnpm test"

####################
# FILE MANAGEMENT
####################

# Aider ignore file
aiderignore: .aiderignore

# Add gitignored files to scope
add-gitignore-files: false

# Only files in current subtree
subtree-only: false

####################
# HISTORY & CACHING
####################

# History files
input-history-file: .aider.input.history
chat-history-file: .aider.chat.history.md
llm-history-file: .aider.llm.history

# Restore previous session
restore-chat-history: false

# Max chat history before summarization
max-chat-history-tokens: 2048

# Prompt caching (Anthropic models)
cache-prompts: true

# Keep cache warm (N pings × 5 min)
cache-keepalive-pings: 12  # 60 minutes

####################
# DISPLAY & UI
####################

# Color scheme
dark-mode: true

# Output styling
pretty: true
stream: true
show-diffs: false

# Custom colors (hex codes)
user-input-color: "#00cc00"
tool-output-color: "#808080"
tool-error-color: "#FF2222"
assistant-output-color: "#0088ff"

# Code theme (Pygments style)
code-theme: monokai

####################
# VOICE INPUT
####################

# Voice settings (optional)
voice-format: wav
voice-language: en

####################
# WATCH MODE
####################

# Watch for AI comments in files
watch-files: false

####################
# USER EXPERIENCE
####################

# Auto-accept confirmations
yes-always: false

# VI editing mode
vim: false

# Enhanced input
fancy-input: true

# Multi-line input (Meta-Enter)
multiline: false

# Notifications
notifications: false

# Suggest shell commands
suggest-shell-commands: true

# Detect and add URLs
detect-urls: true

# Encoding
encoding: utf-8

# Line endings
line-endings: platform  # Options: platform, unix, windows

####################
# ANALYTICS
####################

# Permanently disable analytics
analytics-disable: true

# Disable for session
analytics: false

####################
# ADVANCED
####################

# Environment file
env-file: .env

# Execute commands from file on launch
# load: startup.txt

# Debugging
verbose: false
show-repo-map: false
show-prompts: false
dry-run: false

# Skip repo sanity checks
skip-sanity-check-repo: false

# Check for updates
check-update: true
show-release-notes: true
Enter fullscreen mode Exit fullscreen mode

Critical Settings Explained

model vs weak-model

model: The primary LLM for code generation

  • Used for: Code edits, refactoring, complex reasoning
  • Recommendation: Use most capable model (Claude 3.5 Sonnet, GPT-4o)
  • Cost: Higher per token

weak-model: Cheaper model for routine tasks

  • Used for: Commit messages, file summaries, simple operations
  • Recommendation: Use fast, cheap model (GPT-4o-mini, GPT-3.5-turbo)
  • Cost: 10-20x cheaper than primary model

Cost Optimization Example:

model: claude-3-5-sonnet-20241022        # $3/1M input tokens
weak-model: gpt-4o-mini                  # $0.15/1M input tokens

# This setup saves 90% on commit messages while maintaining quality
Enter fullscreen mode Exit fullscreen mode

edit-format Options

Format Description Use Case
diff Git-style diff with <<<<<<< SEARCH markers Default, reliable
diff-fenced Diff with file path in fence Better for long files
udiff Unified diff format Complex multi-line changes
whole Return entire file content Small files, complete rewrites
editor-diff Streamlined diff (architect mode) Two-step editing
editor-whole Whole file (architect mode) Two-step with full rewrites

Recommendation: Start with diff (default), switch to udiff if you encounter complex refactoring.

Architect Mode

Two-Step Editing Process:

  1. Planning Phase: Main model analyzes and plans changes
  2. Editing Phase: Editor model applies changes using streamlined format

Configuration:

architect: true                      # Enable architect mode
editor-model: claude-3-5-sonnet-20241022  # Model for applying edits
editor-edit-format: editor-diff      # Format for editor model
auto-accept-architect: true          # Auto-apply planned changes
Enter fullscreen mode Exit fullscreen mode

When to Use:

  • Large refactoring across multiple files
  • Complex architectural changes
  • When you want AI to think through changes before applying

Repository Map

What It Does: Provides AI with high-level codebase structure (files, functions, classes) without full content.

Token Budget:

map-tokens: 1024    # Default (good for medium projects)
map-tokens: 2048    # Large codebases
map-tokens: 512     # Small projects or token conservation
map-tokens: 0       # Disable entirely
Enter fullscreen mode Exit fullscreen mode

Refresh Strategy:

map-refresh: auto      # Refresh when files change (default)
map-refresh: always    # Regenerate every request (slow but accurate)
map-refresh: files     # Only when file list changes
map-refresh: manual    # Only with /map-refresh command
Enter fullscreen mode Exit fullscreen mode

Pro Tip: For codebases >100 files, map-tokens: 2048 significantly improves AI's architectural awareness.


4. Conventions Files: Your Rules Repository {#4-conventions-files}

The Conventions File Philosophy

Aider treats conventions files as persistent project memory. Unlike inline rules (limited to short directives), conventions files provide:

  • Rich context: Project history, architecture decisions, team preferences
  • Detailed patterns: Code examples, anti-patterns, gotchas
  • Living documentation: Single source of truth for humans and AI

CONVENTIONS.md Structure Template

# Project Coding Conventions

**Version**: 1.0.0  
**Last Updated**: 2025-11-29  
**Maintainers**: @team-lead, @architect  

---

## Project Overview

**Name**: E-Commerce Platform API  
**Purpose**: RESTful API for multi-tenant e-commerce  
**Stack**: FastAPI, PostgreSQL, Redis, Docker  

---

## Technology Stack

### Backend
- **Language**: Python 3.12
- **Framework**: FastAPI 0.110
- **Database**: PostgreSQL 16 with AsyncPG
- **ORM**: SQLAlchemy 2.0 (async)
- **Caching**: Redis 7.2
- **Task Queue**: Celery with Redis broker

### Testing
- **Framework**: pytest 8.0
- **Mocking**: pytest-mock, pytest-asyncio
- **Coverage**: pytest-cov (minimum 80%)

### Code Quality
- **Linter**: ruff (replacing flake8/isort)
- **Formatter**: black (line length: 100)
- **Type Checker**: mypy (strict mode)

---

## Project Structure

Enter fullscreen mode Exit fullscreen mode

src/
├── api/
│ ├── routes/ # FastAPI routers
│ ├── dependencies.py # Dependency injection
│ └── middleware.py # Custom middleware
├── core/
│ ├── config.py # Settings management
│ ├── security.py # Auth utilities
│ └── exceptions.py # Custom exceptions
├── domain/
│ ├── models/ # Business entities (dataclasses)
│ ├── services/ # Business logic
│ └── repositories/ # Data access interfaces
├── infrastructure/
│ ├── database/ # DB implementation
│ ├── cache/ # Redis implementation
│ └── external_apis/ # Third-party integrations
└── tests/
├── unit/
├── integration/
└── e2e/


---

## Coding Standards

### Python Style Guide

#### Type Hints (Required)
All functions must have type hints:

Enter fullscreen mode Exit fullscreen mode


python

✅ CORRECT

from typing import Optional

async def get_user(user_id: int) -> Optional[User]:
user = await db.query(User).filter(User.id == user_id).first()
return user

❌ WRONG - No type hints

async def get_user(user_id):
user = await db.query(User).filter(User.id == user_id).first()
return user


#### Async/Await (Required for I/O)
All I/O operations must be async:

Enter fullscreen mode Exit fullscreen mode


python

✅ CORRECT

async def fetch_data(url: str) -> dict:
async with httpx.AsyncClient() as client:
response = await client.get(url)
return response.json()

❌ WRONG - Blocking I/O

def fetch_data(url: str) -> dict:
response = requests.get(url) # Blocks event loop!
return response.json()


#### Error Handling Pattern
Use custom exceptions with proper logging:

Enter fullscreen mode Exit fullscreen mode


python

✅ CORRECT

from core.exceptions import ResourceNotFoundError
import structlog

logger = structlog.get_logger()

async def get_order(order_id: int) -> Order:
order = await order_repository.get_by_id(order_id)

if not order:
    logger.warning("order_not_found", order_id=order_id)
    raise ResourceNotFoundError(f"Order {order_id} not found")

return order
Enter fullscreen mode Exit fullscreen mode

❌ WRONG - Generic exception

async def get_order(order_id: int) -> Order:
order = await order_repository.get_by_id(order_id)
if not order:
raise Exception("Not found") # Too generic!
return order


#### Docstrings (Required for Public APIs)
Use Google-style docstrings:

Enter fullscreen mode Exit fullscreen mode


python
async def create_user(
email: str,
password: str,
role: UserRole = UserRole.CUSTOMER
) -> User:
"""Create a new user account.

Args:
    email: User's email address (must be unique)
    password: Plain text password (will be hashed)
    role: User role (defaults to CUSTOMER)

Returns:
    Created user instance with hashed password

Raises:
    DuplicateEmailError: If email already exists
    ValidationError: If email/password invalid

Example:
    >>> user = await create_user("user@example.com", "securepass123")
    >>> print(user.id)
    42
"""
# Implementation...
Enter fullscreen mode Exit fullscreen mode

---

## Design Patterns

### Repository Pattern (Required for Data Access)

**Interface Definition**:
Enter fullscreen mode Exit fullscreen mode


python

domain/repositories/user_repository.py

from abc import ABC, abstractmethod
from typing import Optional, List

class UserRepository(ABC):
@abstractmethod
async def get_by_id(self, user_id: int) -> Optional[User]:
pass

@abstractmethod
async def get_by_email(self, email: str) -> Optional[User]:
    pass

@abstractmethod
async def create(self, user: User) -> User:
    pass

@abstractmethod
async def update(self, user: User) -> User:
    pass
Enter fullscreen mode Exit fullscreen mode

**Implementation**:
Enter fullscreen mode Exit fullscreen mode


python

infrastructure/database/repositories/user_repository_impl.py

from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select

class UserRepositoryImpl(UserRepository):
def init(self, session: AsyncSession):
self.session = session

async def get_by_id(self, user_id: int) -> Optional[User]:
    stmt = select(User).where(User.id == user_id)
    result = await self.session.execute(stmt)
    return result.scalar_one_or_none()

async def create(self, user: User) -> User:
    self.session.add(user)
    await self.session.flush()  # Get ID without committing
    await self.session.refresh(user)
    return user
Enter fullscreen mode Exit fullscreen mode

### Dependency Injection (FastAPI)

**Dependencies Module**:
Enter fullscreen mode Exit fullscreen mode


python

api/dependencies.py

from fastapi import Depends
from sqlalchemy.ext.asyncio import AsyncSession
from typing import Annotated

from infrastructure.database import get_db_session
from domain.repositories import UserRepository
from infrastructure.database.repositories import UserRepositoryImpl

async def get_user_repository(
session: AsyncSession = Depends(get_db_session)
) -> UserRepository:
return UserRepositoryImpl(session)

Type alias for cleaner route signatures

UserRepositoryDep = Annotated[UserRepository, Depends(get_user_repository)]


**Usage in Routes**:
Enter fullscreen mode Exit fullscreen mode


python

api/routes/users.py

from fastapi import APIRouter
from api.dependencies import UserRepositoryDep

router = APIRouter()

@router.get("/users/{user_id}")
async def get_user(
user_id: int,
user_repo: UserRepositoryDep # Auto-injected
) -> UserResponse:
user = await user_repo.get_by_id(user_id)
if not user:
raise HTTPException(status_code=404, detail="User not found")
return UserResponse.from_orm(user)


---

## Testing Standards

### Test Structure (AAA Pattern)

Enter fullscreen mode Exit fullscreen mode


python

tests/unit/services/test_user_service.py

import pytest
from unittest.mock import AsyncMock

@pytest.mark.asyncio
async def test_create_user_success():
# ARRANGE: Set up mocks and test data
mock_repo = AsyncMock(spec=UserRepository)
mock_repo.get_by_email.return_value = None # Email available
mock_repo.create.return_value = User(id=1, email="test@example.com")

service = UserService(mock_repo)

# ACT: Execute the function being tested
result = await service.create_user("test@example.com", "password123")

# ASSERT: Verify outcome
assert result.id == 1
assert result.email == "test@example.com"
mock_repo.create.assert_called_once()
Enter fullscreen mode Exit fullscreen mode

@pytest.mark.asyncio
async def test_create_user_duplicate_email():
# ARRANGE: Existing user with same email
mock_repo = AsyncMock(spec=UserRepository)
existing_user = User(id=1, email="test@example.com")
mock_repo.get_by_email.return_value = existing_user

service = UserService(mock_repo)

# ACT & ASSERT: Verify exception raised
with pytest.raises(DuplicateEmailError) as exc_info:
    await service.create_user("test@example.com", "password123")

assert "already exists" in str(exc_info.value)
Enter fullscreen mode Exit fullscreen mode

### Coverage Requirements
- **Minimum**: 80% overall coverage
- **Critical paths**: 100% coverage (auth, payments, data integrity)
- **Exclusions**: DTOs, simple models, third-party integrations

### Test Commands
Enter fullscreen mode Exit fullscreen mode


bash

Run all tests

pytest

Run with coverage

pytest --cov=src --cov-report=html

Run only unit tests

pytest tests/unit/

Run specific test file

pytest tests/unit/services/test_user_service.py

Run tests matching pattern

pytest -k "test_create_user"


---

## API Design Patterns

### RESTful Endpoint Structure

Enter fullscreen mode Exit fullscreen mode


python

Standard CRUD endpoints

POST /api/v1/users # Create user
GET /api/v1/users # List users (paginated)
GET /api/v1/users/{id} # Get single user
PATCH /api/v1/users/{id} # Partial update
PUT /api/v1/users/{id} # Full replace (rarely used)
DELETE /api/v1/users/{id} # Delete user

Nested resources

GET /api/v1/users/{id}/orders # User's orders
POST /api/v1/users/{id}/orders # Create order for user

Actions (non-CRUD operations)

POST /api/v1/users/{id}/activate # Activate user
POST /api/v1/users/{id}/reset-password # Password reset


### Request/Response Format

**Standard Success Response**:
Enter fullscreen mode Exit fullscreen mode


json
{
"success": true,
"data": {
"id": 42,
"email": "user@example.com",
"created_at": "2025-11-29T10:00:00Z"
},
"meta": {
"timestamp": "2025-11-29T10:00:01Z",
"request_id": "uuid-here"
}
}


**Standard Error Response**:
Enter fullscreen mode Exit fullscreen mode


json
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid email format",
"details": {
"field": "email",
"constraint": "email_format"
}
},
"meta": {
"timestamp": "2025-11-29T10:00:01Z",
"request_id": "uuid-here"
}
}


### Pagination (Offset-Based)

Enter fullscreen mode Exit fullscreen mode


python

Query parameters

GET /api/v1/users?limit=20&offset=40

Response structure

{
"success": true,
"data": [...],
"pagination": {
"limit": 20,
"offset": 40,
"total": 150,
"has_more": true
}
}


---

## Security Requirements

### Authentication
- JWT tokens in `Authorization: Bearer <token>` header
- Access token: 15 minute expiry
- Refresh token: 7 day expiry, rotated on use
- Token storage: HttpOnly cookies (web) or secure storage (mobile)

### Input Validation
- **ALWAYS** validate with Pydantic models
- Sanitize SQL inputs (use parameterized queries via ORM)
- Validate file uploads (type, size, content)
- Rate limiting: 100 req/min per IP (public), 1000 req/min (authenticated)

### Sensitive Data
- **NEVER** log passwords, tokens, credit cards
- **NEVER** commit secrets to git (use .env)
- Hash passwords with bcrypt (cost factor: 12)
- Encrypt sensitive DB columns (PII: email, phone, address)

---

## Performance Guidelines

### Database Optimization
- **Use indexes** on foreign keys and frequently queried columns
- **Avoid N+1 queries**: Use `selectinload()` or `joinedload()`
- **Connection pooling**: Max 20 connections (PostgreSQL)
- **Query timeout**: 30 seconds maximum

### Caching Strategy
Enter fullscreen mode Exit fullscreen mode


python

Cache expensive queries (Redis)

@cache(ttl=300) # 5 minutes
async def get_popular_products() -> List[Product]:
return await product_repo.get_top_sellers(limit=10)

Invalidate on write

async def update_product(product_id: int, data: dict):
await product_repo.update(product_id, data)
await cache.delete(f"product:{product_id}")
await cache.delete("popular_products")


### Async Best Practices
- **Do**: Use async for I/O (DB, HTTP, file ops)
- **Don't**: Use async for CPU-bound tasks (use threading/multiprocessing)
- **Gather concurrent operations**:
Enter fullscreen mode Exit fullscreen mode


python

✅ Parallel (faster)

user, orders, reviews = await asyncio.gather(
user_repo.get_by_id(user_id),
order_repo.get_by_user(user_id),
review_repo.get_by_user(user_id)
)

❌ Sequential (slower)

user = await user_repo.get_by_id(user_id)
orders = await order_repo.get_by_user(user_id)
reviews = await review_repo.get_by_user(user_id)


---

## Common Pitfalls

### Pitfall 1: Blocking I/O in Async Functions
**Symptom**: Application hangs or becomes unresponsive

**Bad**:
Enter fullscreen mode Exit fullscreen mode


python
async def process_image(file_path: str):
with open(file_path, 'rb') as f: # BLOCKS event loop!
data = f.read()
return process(data)


**Good**:
Enter fullscreen mode Exit fullscreen mode


python
import aiofiles

async def process_image(file_path: str):
async with aiofiles.open(file_path, 'rb') as f:
data = await f.read() # Non-blocking
return process(data)


### Pitfall 2: Transaction Management
**Symptom**: Partial updates on errors, data inconsistency

**Bad**:
Enter fullscreen mode Exit fullscreen mode


python
async def transfer_funds(from_id: int, to_id: int, amount: float):
await account_repo.debit(from_id, amount)
# If this fails, first operation already committed!
await account_repo.credit(to_id, amount)


**Good**:
Enter fullscreen mode Exit fullscreen mode


python
async def transfer_funds(from_id: int, to_id: int, amount: float):
async with db.transaction(): # Atomic
await account_repo.debit(from_id, amount)
await account_repo.credit(to_id, amount)
# Both succeed or both rollback


### Pitfall 3: Missing Migration Rollback
**Symptom**: Can't undo database changes

**Always include down migration**:
Enter fullscreen mode Exit fullscreen mode


python

migrations/versions/xxx_add_user_status.py

def upgrade():
op.add_column('users', sa.Column('status', sa.String(20), nullable=False))

def downgrade():
op.drop_column('users', 'status') # Rollback strategy


---

## Development Workflow

### Before Committing
1. Run linter: `ruff check --fix src/`
2. Run formatter: `black src/`
3. Type check: `mypy src/`
4. Run tests: `pytest --cov=src`
5. Check coverage: Must be ≥80%

### Git Commit Format
Follow Conventional Commits:

Enter fullscreen mode Exit fullscreen mode

type(scope): description

feat(auth): add password reset flow
fix(api): handle null user in profile endpoint
docs(readme): update installation instructions
test(users): add edge cases for email validation
refactor(db): extract repository base class


Types: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`, `perf`

---

## External Resources

- **Project Wiki**: https://wiki.company.com/ecommerce-api
- **Architecture Diagrams**: docs/architecture/
- **API Documentation**: http://localhost:8000/docs (Swagger)
- **Slack**: #api-dev-team

---

*This conventions file is the source of truth for our codebase. When in doubt, refer here or ask in #api-dev-team.*
Enter fullscreen mode Exit fullscreen mode

Loading Conventions in .aider.conf.yml

# .aider.conf.yml

model: claude-3-5-sonnet-20241022
weak-model: gpt-4o-mini

# Load conventions file
read:
  - CONVENTIONS.md

# Conventions are cached by Anthropic (efficient)
cache-prompts: true
cache-keepalive-pings: 12  # Keep cache warm for 60 min
Enter fullscreen mode Exit fullscreen mode

Why External Files Beat Inline Rules

Token Efficiency:

  • Anthropic caches external files (50% cost reduction on repeated loads)
  • Inline rules array included in every request (no caching)

Maintainability:

  • Markdown easier to edit than YAML
  • Git diffs more readable
  • Multiple team members can edit simultaneously

Richness:

  • Code examples with syntax highlighting
  • Tables, diagrams, structured content
  • Links to external documentation

Human + AI Aligned:

  • Same document serves onboarding and AI guidance
  • Single source of truth (no sync issues)
  • Developers naturally keep it updated

5. Model Configuration and Selection {#5-model-configuration}

Supported Providers and Models

Provider Configuration Example Models
OpenAI model: gpt-4o gpt-4o, gpt-4o-mini, o1-preview, o1-mini
Anthropic model: claude-3-5-sonnet-20241022 claude-3-5-sonnet, claude-3-opus, claude-3-haiku
Google model: gemini/gemini-2.0-flash-exp gemini-2.0-flash, gemini-1.5-pro
Ollama (Local) model: ollama/qwen2.5-coder qwen2.5-coder, deepseek-coder, codellama
DeepSeek model: deepseek/deepseek-reasoner deepseek-reasoner (R1)
OpenRouter model: openrouter/... Any OpenRouter model

Model Selection Strategy

By Task Type:

# For complex refactoring and architecture
model: claude-3-5-sonnet-20241022     # Best reasoning, $3/1M tokens

# For routine changes and bug fixes
model: gpt-4o                         # Fast, reliable, $2.50/1M tokens

# For quick edits and summaries
weak-model: gpt-4o-mini               # Very fast, $0.15/1M tokens

# For cost-conscious development
model: ollama/qwen2.5-coder:7b        # Free (local), good quality
Enter fullscreen mode Exit fullscreen mode

By Project Budget:

Budget Level Main Model Weak Model Monthly Cost (est.)
Premium claude-3-opus gpt-4o-mini $150-300
Standard claude-3-5-sonnet gpt-4o-mini $50-150
Budget gpt-4o gpt-3.5-turbo $20-50
Free ollama/qwen2.5-coder ollama/qwen2.5-coder $0

Model Aliases for Quick Switching

# Define aliases once
alias:
  - "sonnet:claude-3-5-sonnet-20241022"
  - "opus:claude-3-opus-20240229"
  - "haiku:claude-3-haiku-20240307"
  - "gpt4:gpt-4o"
  - "fast:gpt-4o-mini"
  - "o1:o1-preview"
  - "local:ollama/qwen2.5-coder:7b"
  - "r1:deepseek/deepseek-reasoner"

# Use from command line
# $ aider --model sonnet
# $ aider --model fast
# $ aider --model local
Enter fullscreen mode Exit fullscreen mode

API Key Configuration

Environment Variables (Recommended):

# ~/.bashrc or ~/.zshrc
export OPENAI_API_KEY=sk-...
export ANTHROPIC_API_KEY=sk-ant-...
export GEMINI_API_KEY=...
export DEEPSEEK_API_KEY=...
Enter fullscreen mode Exit fullscreen mode

In .aider.conf.yml (NOT RECOMMENDED - secrets in git):

# ❌ Don't do this (exposes secrets)
openai-api-key: sk-...

# ✅ Reference env vars instead
# (Keys loaded automatically from environment)
Enter fullscreen mode Exit fullscreen mode

Using .env File:

# .aider.conf.yml
env-file: .env  # Load keys from .env

# .env (add to .gitignore!)
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
Enter fullscreen mode Exit fullscreen mode

Custom API Endpoints

For Azure OpenAI:

model: azure/gpt-4o
openai-api-base: https://your-resource.openai.azure.com/
set-env:
  - OPENAI_API_TYPE=azure
  - OPENAI_API_VERSION=2024-02-15-preview
Enter fullscreen mode Exit fullscreen mode

For OpenRouter:

model: openrouter/anthropic/claude-3-5-sonnet
openai-api-base: https://openrouter.ai/api/v1
openai-api-key: ${OPENROUTER_API_KEY}
Enter fullscreen mode Exit fullscreen mode

For Local Ollama:

model: ollama/qwen2.5-coder:7b
# No API key needed
# Ollama must be running: `ollama serve`
Enter fullscreen mode Exit fullscreen mode

Reasoning Models Configuration

OpenAI o1/o3 Models:

model: o1-preview
reasoning-effort: medium  # Options: low, medium, high
Enter fullscreen mode Exit fullscreen mode

Anthropic Claude 3.7 Sonnet:

model: claude-3.7-sonnet-20250219
thinking-tokens: 8000  # Budget for extended thinking
Enter fullscreen mode Exit fullscreen mode

DeepSeek R1:

model: deepseek/deepseek-reasoner
# Reasoning is automatic
Enter fullscreen mode Exit fullscreen mode

6. Git Integration and Attribution {#6-git-integration}

Auto-Commit Behavior

Basic Configuration:

# Let AI auto-commit changes
auto-commits: true

# Allow commits even if repo has uncommitted changes
dirty-commits: true

# Custom commit message prompt
commit-prompt: "Write a concise commit following Conventional Commits format"
Enter fullscreen mode Exit fullscreen mode

When to Enable Auto-Commits:

  • ✅ Solo projects where you review history later
  • ✅ Experimental branches
  • ✅ Rapid prototyping

When to Disable Auto-Commits:

  • ❌ Team projects requiring review
  • ❌ Production code
  • ❌ When you want manual control

Attribution Strategies

Method 1: Co-authored-by Trailer (Recommended):

attribute-co-authored-by: true  # Preferred method
attribute-author: false
attribute-committer: false
Enter fullscreen mode Exit fullscreen mode

Result:

feat: add user authentication

Co-authored-by: Aider <aider@aider.chat>
Enter fullscreen mode Exit fullscreen mode

Benefits:

  • Standard Git convention
  • GitHub shows co-authors in UI
  • Preserves your name as primary author
  • Clear audit trail

Method 2: Author/Committer Fields:

attribute-author: true       # Sets git author name
attribute-committer: true    # Sets git committer name
Enter fullscreen mode Exit fullscreen mode

Result:

Author: Aider <aider@aider.chat>
Committer: Your Name <you@email.com>
Enter fullscreen mode Exit fullscreen mode

Method 3: Commit Message Prefix:

attribute-commit-message-author: true      # Prefix if Aider authored
attribute-commit-message-committer: true   # Prefix all commits
Enter fullscreen mode Exit fullscreen mode

Result:

aider: feat: add user authentication
Enter fullscreen mode Exit fullscreen mode

Recommended Configuration for Teams:

auto-commits: false                # Manual review required
attribute-co-authored-by: true     # Credit Aider as co-author
show-diffs: true                   # Show changes before committing
Enter fullscreen mode Exit fullscreen mode

Custom Commit Messages

Default Behavior: Aider uses weak model to generate commit message.

Customize Prompt:

commit-prompt: |
  Write a commit message following our standards:
  - Format: type(scope): description
  - Types: feat, fix, docs, style, refactor, test, chore
  - Max 72 characters for first line
  - Include ticket number if applicable
  - Example: feat(auth): add OAuth login [PROJ-123]
Enter fullscreen mode Exit fullscreen mode

Commit Message Language:

commit-language: en  # English (default)
commit-language: es  # Spanish
commit-language: de  # German
Enter fullscreen mode Exit fullscreen mode

Skip Git Hooks

Problem: Pre-commit hooks may slow down or block Aider.

Solution:

git-commit-verify: false  # Skip pre-commit hooks
Enter fullscreen mode Exit fullscreen mode

Result: Equivalent to git commit --no-verify

When to Use:

  • Pre-commit runs expensive checks (linting, formatting)
  • You handle checks separately (via auto-lint in Aider)
  • Pre-commit requires interactive input

7. Linting and Testing Automation {#7-linting-testing}

Auto-Linting Configuration

Enable Auto-Linting:

auto-lint: true

lint-cmd:
  - "python: ruff check --fix --select I,F,E,W"
  - "javascript: eslint --fix"
  - "typescript: eslint --fix"
  - "rust: cargo clippy --fix --allow-dirty"
Enter fullscreen mode Exit fullscreen mode

How It Works:

  1. AI makes code changes
  2. Aider identifies affected file types
  3. Runs corresponding lint command
  4. If linter fixes issues, shows diff
  5. You approve or reject

Lint Command Format:

"filetype: command {filename}"
Enter fullscreen mode Exit fullscreen mode

Variables:

  • {filename}: Replaced with actual file path
  • If omitted, filenames passed as arguments

Examples:

lint-cmd:
  # Ruff (Python) - auto-fix
  - "python: ruff check --fix --select I,F,E,W"

  # ESLint (JavaScript/TypeScript) - auto-fix
  - "javascript: eslint --fix"
  - "typescript: eslint --fix"

  # Prettier (multi-language) - format
  - "javascript: prettier --write {filename}"
  - "typescript: prettier --write {filename}"

  # Black (Python) - format
  - "python: black {filename}"

  # Cargo Clippy (Rust) - lint with fixes
  - "rust: cargo clippy --fix --allow-dirty"

  # Go - format
  - "go: gofmt -w {filename}"
Enter fullscreen mode Exit fullscreen mode

Pro Tip: Combine linting and formatting:

lint-cmd:
  - "python: ruff check --fix && black {filename}"
  - "typescript: eslint --fix {filename} && prettier --write {filename}"
Enter fullscreen mode Exit fullscreen mode

Auto-Testing Configuration

Enable Auto-Testing:

auto-test: true
test-cmd: "pytest tests/"
Enter fullscreen mode Exit fullscreen mode

How It Works:

  1. AI makes code changes
  2. Aider runs test command
  3. If tests fail, AI sees output
  4. AI attempts to fix failing tests
  5. Process repeats until tests pass

When to Enable:

  • ✅ Fast test suite (<30 seconds)
  • ✅ Unit tests only
  • ✅ TDD workflow

When to Disable:

  • ❌ Slow integration/E2E tests
  • ❌ Tests requiring manual setup
  • ❌ Flaky tests

Selective Testing:

# Run only affected tests
test-cmd: "pytest tests/ --lf --tb=short"  # Last failed, short traceback

# Run with coverage
test-cmd: "pytest tests/ --cov=src --cov-fail-under=80"

# Run specific test suite
test-cmd: "pytest tests/unit/"  # Only unit tests
Enter fullscreen mode Exit fullscreen mode

Linting Strategies by Language

Python (Modern Stack):

lint-cmd:
  - "python: ruff check --fix --select I,F,E,W,C90,UP"  # Import, errors, warnings, complexity, upgrades
  - "python: black --line-length 100"  # Format
  - "python: mypy --strict"  # Type check
Enter fullscreen mode Exit fullscreen mode

TypeScript/JavaScript:

lint-cmd:
  - "typescript: eslint --fix --max-warnings 0"
  - "typescript: prettier --write"
  - "typescript: tsc --noEmit"  # Type check only
Enter fullscreen mode Exit fullscreen mode

Rust:

lint-cmd:
  - "rust: cargo fmt"  # Format
  - "rust: cargo clippy --fix --allow-dirty -- -D warnings"  # Lint
Enter fullscreen mode Exit fullscreen mode

Go:

lint-cmd:
  - "go: gofmt -w"
  - "go: go vet"
  - "go: staticcheck"
Enter fullscreen mode Exit fullscreen mode

8. Advanced Model Settings {#8-advanced-settings}

.aider.model.settings.yml File

Purpose: Configure custom or unknown models not in Aider's built-in list.

Location: Same directory as .aider.conf.yml

Reference in Config:

# .aider.conf.yml
model-settings-file: .aider.model.settings.yml
model: openrouter/qwen/qwen2.5-coder-32b-instruct
Enter fullscreen mode Exit fullscreen mode

Schema:

# .aider.model.settings.yml

- name: openrouter/qwen/qwen2.5-coder-32b-instruct
  edit_format: diff                    # Default edit format
  weak_model_name: openrouter/qwen/qwen2.5-coder-32b-instruct  # For summaries
  use_repo_map: true                   # Enable repo map
  send_undo_reply: true                # Support undo
  editor_model_name: openrouter/anthropic/claude-3.7-sonnet  # Architect mode editor
  editor_edit_format: editor-diff

- name: ollama/deepseek-coder:6.7b
  edit_format: whole                   # Local models better with whole files
  use_repo_map: false                  # Smaller context window
  send_undo_reply: false
  lazy: true                           # Don't load until needed

- name: azure/gpt-4o
  edit_format: diff
  use_repo_map: true
  max_chat_history_tokens: 4096
Enter fullscreen mode Exit fullscreen mode

Key Fields:

Field Type Description
name Model ID from provider
edit_format diff, whole, udiff How AI returns code
weak_model_name string For summaries
use_repo_map boolean Enable repository context
send_undo_reply boolean Support undoing changes
lazy boolean Don't load until first request
editor_model_name string Model for architect mode
editor_edit_format string Format for editor model
reminder string Append to system prompt
examples_as_sys_msg boolean Examples in system prompt
use_temperature boolean Allow temperature setting

Configuring OpenRouter Models

# .aider.model.settings.yml
- name: openrouter/anthropic/claude-3-5-sonnet
  edit_format: diff
  use_repo_map: true

- name: openrouter/google/gemini-flash-1.5
  edit_format: whole
  use_repo_map: true
Enter fullscreen mode Exit fullscreen mode

Note: Aider supports many models out of the box. Only use .aider.model.settings.yml for new models or to customize defaults.

Model Metadata (.aider.model.metadata.json)

Purpose: Override token limits and pricing for models.

Schema:

{
  "claude-3-5-sonnet-20241022": {
    "max_tokens": 8192,
    "max_input_tokens": 200000,
    "input_cost_per_token": 0.000003,
    "output_cost_per_token": 0.000015
  }
}
Enter fullscreen mode Exit fullscreen mode

Usage:

# .aider.conf.yml
model-metadata-file: .aider.model.metadata.json
Enter fullscreen mode Exit fullscreen mode

9. Production Configuration Examples {#9-production-examples}

Example 1: Team Collaboration (Safety First)

Target: Professional teams using GitHub/GitLab, requiring code review.

# .aider.conf.yml

# Use best model for quality
model: claude-3-5-sonnet-20241022
weak-model: gpt-4o-mini

# Team conventions
read:
  - CONVENTIONS.md
  - docs/architecture.md

# Quality gates
auto-lint: true
lint-cmd:
  - "python: ruff check --fix"
  - "typescript: eslint --fix"

# Manual review required
auto-commits: false
show-diffs: true

# Proper attribution
attribute-co-authored-by: true

# Exclude sensitive/large files
aiderignore: .aiderignore
add-gitignore-files: false

# Repository context
map-tokens: 2048
map-refresh: auto

# Caching
cache-prompts: true
Enter fullscreen mode Exit fullscreen mode

Example 2: Solo Developer (Speed & Automation)

Target: Individual developer building MVP, wants maximum automation.

# .aider.conf.yml

# Fast, capable model
model: gpt-4o
weak-model: gpt-4o-mini

# Auto-everything
auto-commits: true
dirty-commits: true
auto-lint: true
auto-test: true

# Commands
lint-cmd:
  - "python: ruff check --fix"
test-cmd: "pytest tests/unit"

# Minimal context needed
map-tokens: 1024
read: [CONVENTIONS.md]

# UI
dark-mode: true
pretty: true
Enter fullscreen mode Exit fullscreen mode

Example 3: Local-Only (Privacy & Cost)

Target: Sensitive project or offline development.

# .aider.conf.yml

# Local model via Ollama
model: ollama/qwen2.5-coder:14b
weak-model: ollama/qwen2.5-coder:1.5b

# Disable telemetry
analytics-disable: true
check-update: false

# Adjust for local model capabilities
edit-format: whole             # Local models struggle with diffs
map-tokens: 0                  # Disable repo map (save context)
auto-commits: false            # Verify local model output

# UI
dark-mode: true
Enter fullscreen mode Exit fullscreen mode

Example 4: Enterprise Monorepo

Target: Large codebase, multiple languages, strict compliance.

# .aider.conf.yml

# Enterprise-grade model
model: claude-3-5-sonnet-20241022
weak-model: gpt-4o-mini

# Strict boundaries
subtree-only: true             # Focus on current directory
map-tokens: 4096               # Large map for context
map-refresh: manual            # Don't refresh constantly

# Compliance
analytics-disable: true
attribute-co-authored-by: true
llm-history-file: null         # Don't log prompts

# Standards
read:
  - CONVENTIONS.md
  - ../../GLOBAL_STANDARDS.md  # Monorepo root

# Linting
lint-cmd:
  - "python: ruff check --fix"
  - "typescript: eslint --fix --max-warnings 0"

# Custom commit format
commit-prompt: "Format: type(scope): description [JIRA-ID]"
Enter fullscreen mode Exit fullscreen mode

10. Rules Writing Best Practices {#10-rules-best-practices}

High-Density Rules

Bad (Verbose):

When you write Python code, please try to use type hints if possible because it helps us catch errors. Also, we prefer using list comprehensions instead of loops when it makes sense.
Enter fullscreen mode Exit fullscreen mode

Good (Dense):

## Python Standards
- **Type Hints**: Required for all function arguments and return values.
- **Loops**: Prefer list comprehensions for simple transformations.
- **Style**: Follow PEP 8 (enforced by Ruff).
Enter fullscreen mode Exit fullscreen mode

Context Engineering

Bad (Vague):

Use good error handling.
Enter fullscreen mode Exit fullscreen mode

Good (Pattern-Based):

## Error Handling Pattern
- Use custom exceptions from `core.exceptions`
- Log errors with `structlog` before raising
- Example:
Enter fullscreen mode Exit fullscreen mode


python
try:
await process()
except APIError as e:
logger.error("api_failed", error=str(e))
raise ServiceUnavailableError from e

Enter fullscreen mode Exit fullscreen mode

Anti-Patterns Section

Explicitly listing what not to do is highly effective.

## Anti-Patterns
-**No global state**: Do not use global variables.
-**No wildcard imports**: `from module import *` is forbidden.
-**No print statements**: Use `logger` instead.
-**No hardcoded secrets**: Use `os.getenv()`.
Enter fullscreen mode Exit fullscreen mode

11. Testing and Validation {#11-testing-validation}

Validate Configuration

Run Aider in dry-run mode to verify configuration loading:

aider --dry-run --verbose
Enter fullscreen mode Exit fullscreen mode

Check Output:

  • "Loaded config from .aider.conf.yml"
  • "Model: claude-3-5-sonnet-20241022"
  • "Git repo: .git"
  • "Repo map: using 1024 tokens"

Test Rules Adherence

Prompt:

/ask What are the coding standards for this project based on CONVENTIONS.md?
Enter fullscreen mode Exit fullscreen mode

Verification:

  • Does Aider list your specific rules?
  • Does it mention anti-patterns?
  • Does it cite the correct tech stack?

Verify Linting Integration

  1. Create a file with a linting error (e.g., unused import).
  2. Ask Aider to edit the file.
  3. Watch logs: "Running lint command..."
  4. Verify Aider fixes the lint error automatically.

12. Troubleshooting Common Issues {#12-troubleshooting}

Issue 1: Configuration Not Loading

Symptom: Aider ignores settings in .aider.conf.yml.

Fixes:

  1. Check filename: Must be exactly .aider.conf.yml (not .yaml).
  2. Check location: Must be in project root or parent directory.
  3. Check syntax: YAML indentation is sensitive. Run through a YAML validator.
  4. Check environment: Environment variables override config files.

Issue 2: "Model Not Found"

Symptom: Error when starting Aider with custom model.

Fixes:

  1. Check API key: Ensure OPENAI_API_KEY or ANTHROPIC_API_KEY is set.
  2. Check model name: Verify spelling against provider docs.
  3. Use settings file: Add unknown models to .aider.model.settings.yml.
  4. Check aliases: Ensure alias is defined in config.

Issue 3: Repository Map Too Slow

Symptom: "Repo map processing..." takes too long.

Fixes:

  1. Increaseignore: Add more files to .aiderignore (node_modules, dist, etc.).
  2. Reduce tokens: Set map-tokens: 512 or 0.
  3. Manual refresh: Set map-refresh: manual.
  4. Use subtree: Set subtree-only: true to focus on current directory.

Issue 4: Auto-Lint Loop

Symptom: Aider gets stuck fixing lint errors repeatedly.

Fixes:

  1. Fix linter config: Ensure linter and formatter don't conflict (e.g., Ruff vs Black).
  2. Relax linter: Remove strict rules from lint-cmd.
  3. Disable auto-lint: Set auto-lint: false temporarily.

Summary Checklist for Success

  • [ ] Config: .aider.conf.yml in project root
  • [ ] Rules: CONVENTIONS.md created and referenced
  • [ ] Ignore: .aiderignore configured
  • [ ] Keys: Environment variables set (not in config)
  • [ ] Model: Best model selected for budget
  • [ ] Git: Auto-commit disabled for teams
  • [ ] Lint: Lint commands configured for language

By following this guide, you transform Aider from a generic coding tool into a specialized team member that knows your project inside and out.

Top comments (0)