Skip to content

Latest commit

 

History

History
784 lines (595 loc) · 17.5 KB

File metadata and controls

784 lines (595 loc) · 17.5 KB

Code Conventions & Standards

Shared documentation for all AI coding assistants

This file is referenced by multiple AI tool configurations. Changes here automatically apply to all tools that support file references.

CRITICAL: No Decorative Emojis

NEVER use decorative emojis in any output.

Prohibited (Decorative)

  • ❌ Section markers: 🚀 🎯 🐳 🛠️ 📦 📚 🤖 🔧 📁 🎨 🔍 📝
  • ❌ Callouts: 👋 📖 🎓 📧 👉 💡 ⚡ 🔥
  • ❌ Celebrations: ✨ 🎉 ❤️ 💪 👏
  • ❌ Status: 🚫 ⚠️ 📢 🎊

Allowed (Functional Only)

  • ✅ Checkboxes for completion status
  • ❌ X-marks for failed/not-included items

Applies To

  • Code output (print statements, CLI messages, logs)
  • Documentation (README, guides, docstrings, comments)
  • Commit messages and PR descriptions
  • Error messages and user-facing text
  • File contents generated by code

Rationale

  • Professional appearance for enterprise use
  • Universal compatibility across terminals/editors
  • Better accessibility for screen readers
  • Consistent with Atyantik branding standards
  • Reduces visual clutter, improves readability

Examples

Code Output:

# ❌ WRONG
print("🚀 Starting process...")
print("✨ Success!")

# ✅ CORRECT
print("Starting process...")
print("Success!")

Documentation:

❌ WRONG: ## 🎯 Quick Start
✅ CORRECT: ## Quick Start

❌ WRONG: **👋 Welcome!** Check out the docs 📖
✅ CORRECT: **Welcome!** Check out the docs

Commit Messages:

❌ WRONG: "✨ Add new feature 🚀"
✅ CORRECT: "Add user authentication feature"

CRITICAL: AI-Generated Summaries Location

ALWAYS store AI-generated summaries in .ai-summary/ directory.

Rules

  • All automated AI execution summaries MUST go in .ai-summary/
  • This directory is in .gitignore - never commit AI summaries
  • Examples: session summaries, execution logs, automated reports
  • Keeps git history clean and focused on actual code changes

Examples

Correct:

# AI writes summary to proper location
.ai-summary/session-2025-11-02.md
.ai-summary/execution-log-20251102-143022.md

Wrong:

# ❌ Never write summaries to root or other directories
./ai-summary.md
./session-notes.md
docs/ai-output.md

Why This Matters

  • Prevents accidental commits of AI-generated content
  • Keeps repository professional and focused
  • Separates code from AI tool artifacts
  • Consistent across all AI assistants

CRITICAL: Git Commits - No AI Co-Authoring

NEVER add AI co-author attribution to git commits.

Rules

  • Do NOT add "Co-Authored-By: Claude" or any AI assistant attribution
  • Do NOT add "Generated with [AI Tool]" footers
  • Keep commit messages professional and human-authored
  • Commits should reflect the human developer's work, not AI involvement

Examples

Correct:

git commit -m "Add user authentication feature

Implemented OAuth2 authentication with JWT tokens.
Added login, logout, and token refresh endpoints.
Includes comprehensive tests with 95% coverage."

Wrong:

# ❌ Never add AI attribution
git commit -m "Add user authentication feature

...

Generated with Claude Code.

Co-Authored-By: Claude <noreply@anthropic.com>"

Why This Matters

  • Commits should represent human intent and responsibility
  • Professional git history without AI attribution
  • Clear accountability for code changes
  • Industry standard practice

Quick Reference

Aspect Standard
Line Length 88 characters (Black default)
Quotes Double quotes preferred
Type Hints Required on all functions (mypy strict)
Docstrings Google style format
Import Sorting isort with Black profile
Python Version 3.11+ (3.13 recommended)

Code Formatting

Black (Auto-formatter)

# Format all code
make format

# Format specific directory
uv run black src tests

Standards:

  • Line length: 88 characters
  • Double quotes for strings
  • Trailing commas in multi-line structures
  • No manual line breaks (Black decides)

Keep Black and Ruff in Agreement

  • Treat Black as the source of truth for layout, then let Ruff confirm it.
  • Avoid patterns the tools format differently. Instead of multi-line assert calls wrapped in parentheses, assign long messages to a local variable and pass that variable to the assertion. This keeps both formatters from oscillating the same lines.
  • After editing formatted code, run pre-commit run --files … (or make format) before staging to catch conflicts immediately.
  • If a rule keeps rewriting your changes, refactor the code so the intent is explicit rather than relying on formatter heuristics.

isort (Import Sorting)

# ✅ Correct import order
from __future__ import annotations  # Future imports first

import json  # Standard library
import re
from typing import Any

import pytest  # Third-party packages
import requests

from python_modern_template import helper  # Local imports
from python_modern_template.utils import process_data

Rules:

  • One import per line
  • Alphabetically sorted within groups
  • Blank line between groups
  • No wildcard imports (from module import *)

Type Hints (Mandatory)

Function Signatures

from __future__ import annotations  # For forward references

from typing import Any

def process_data(
    input_str: str,
    options: dict[str, Any] | None = None,
    max_length: int = 100,
) -> list[str]:
    """Process input data with options.

    Args:
        input_str: Input data to process
        options: Optional processing options
        max_length: Maximum length for output items

    Returns:
        List of processed strings
    """
    ...

Modern Type Syntax (Python 3.10+)

# ✅ Use new union syntax
def get_value(key: str) -> str | None:
    ...

# ❌ Old syntax (avoid)
from typing import Optional
def get_value(key: str) -> Optional[str]:
    ...

# ✅ Use built-in generics
def process_items(items: list[str]) -> dict[str, int]:
    ...

# ❌ Old syntax (avoid)
from typing import List, Dict
def process_items(items: List[str]) -> Dict[str, int]:
    ...

Complex Types

from typing import Any, Callable, Protocol

# Callable types
def apply_function(func: Callable[[int, int], int], a: int, b: int) -> int:
    return func(a, b)

# Protocol for structural typing
class HasName(Protocol):
    name: str

def greet(obj: HasName) -> str:
    return f"Hello, {obj.name}"

# TypedDict for structured dicts
from typing import TypedDict

class UserData(TypedDict):
    name: str
    age: int
    email: str | None

Documentation

Docstring Format (Google Style)

def calculate_total(
    items: list[dict[str, Any]],
    tax_rate: float = 0.1,
    discount: float = 0.0,
) -> float:
    """Calculate total price with tax and discount.

    This function processes a list of items, applies tax and discount,
    and returns the final total price.

    Args:
        items: List of item dictionaries with 'price' key
        tax_rate: Tax rate as decimal (default 0.1 = 10%)
        discount: Discount as decimal (default 0.0 = no discount)

    Returns:
        Total price after tax and discount

    Raises:
        ValueError: If items is empty
        KeyError: If item missing 'price' key

    Example:
        >>> items = [{"name": "Book", "price": 10.00}]
        >>> calculate_total(items, tax_rate=0.1)
        11.00
    """
    if not items:
        raise ValueError("Items list cannot be empty")

    subtotal = sum(item["price"] for item in items)
    total = subtotal * (1 + tax_rate) * (1 - discount)
    return round(total, 2)

Module Docstrings

"""Module for user authentication and authorization.

This module provides functions for:
- User login/logout
- Password validation
- Token generation
- Permission checking

Typical usage example:

    from python_modern_template.auth import authenticate_user

    user = authenticate_user(username, password)
    if user:
        print(f"Welcome, {user.name}")
"""

Class Docstrings

class UserValidator:
    """Validates user data before creating user accounts.

    This class provides validation for:
    - Username format and uniqueness
    - Email format
    - Password strength
    - Age requirements

    Attributes:
        min_age: Minimum age for user registration
        password_min_length: Minimum password length

    Example:
        >>> validator = UserValidator(min_age=18)
        >>> validator.validate_username("john_doe")
        True
    """

    def __init__(self, min_age: int = 13) -> None:
        """Initialize validator with age requirement.

        Args:
            min_age: Minimum age for registration (default 13)
        """
        self.min_age = min_age

Code Quality

Linting Tools

Ruff (Fast Linter)

# Check all code
make lint

# Auto-fix issues
uv run ruff check --fix src tests

# Check specific rules
uv run ruff check src --select=E,F,B

Enabled rules:

  • E/W (pycodestyle errors and warnings)
  • F (pyflakes)
  • B (flake8-bugbear)
  • C4 (flake8-comprehensions)
  • UP (pyupgrade)

Pylint (Comprehensive Linter)

# Check all code (must score 10/10)
uv run pylint src tests

Key rules:

  • No unused variables
  • No unused imports
  • No duplicate code
  • Consistent naming
  • Maximum function complexity: 10

mypy (Type Checker)

# Check type hints
uv run mypy src tests

Configuration (strict mode):

  • All functions must have type hints
  • No implicit Any types
  • No untyped function calls
  • Check return values

Pre-commit Hooks

# Install hooks
make install

# Run manually
make pre-commit

Hooks run on commit:

  1. Trailing whitespace removal
  2. YAML syntax check
  3. Large file prevention
  4. Ruff linting
  5. Black formatting
  6. isort import sorting
  7. mypy type checking

Project Structure

leadership-blog-generator/
├── src/
│   └── python_modern_template/
│       ├── __init__.py        # Package exports & version
│       ├── main.py            # CLI and main functionality
│       └── [modules].py       # Additional modules
├── tests/
│   ├── __init__.py
│   ├── conftest.py            # Shared fixtures
│   └── test_*.py              # Test files mirror src structure
├── scripts/
│   └── ai_tools/              # AI context management tools
├── AI_DOCS/                   # Shared AI documentation
├── .ai-context/               # AI session files
├── pyproject.toml             # Project config & dependencies
├── Makefile                   # Development commands
└── README.md                  # User documentation

Import Conventions

# ✅ Correct - import from package name
from python_modern_template import function_name
from python_modern_template.module import ClassName

# ❌ Wrong - don't use src prefix
from src.python_modern_template import function_name

Package Exports

# File: src/python_modern_template/__init__.py

"""Modern Python template package."""

from .main import generate_blog
from .validators import validate_email, validate_phone

__version__ = "0.1.0"

__all__ = [
    "generate_blog",
    "validate_email",
    "validate_phone",
]

Best Practices

DRY (Don't Repeat Yourself)

# ❌ Bad - duplicate logic
def process_user(name: str) -> str:
    return name.strip().lower().replace(" ", "_")

def process_product(name: str) -> str:
    return name.strip().lower().replace(" ", "_")

# ✅ Good - shared logic
def normalize_name(name: str) -> str:
    """Normalize name to lowercase with underscores."""
    return name.strip().lower().replace(" ", "_")

def process_user(name: str) -> str:
    """Process user name."""
    return normalize_name(name)

def process_product(name: str) -> str:
    """Process product name."""
    return normalize_name(name)

Small, Focused Functions

# ✅ Good - single responsibility
def read_file(path: str) -> str:
    """Read file contents."""
    with open(path) as f:
        return f.read()

def parse_config(content: str) -> dict[str, Any]:
    """Parse config from string."""
    return json.loads(content)

def load_config(path: str) -> dict[str, Any]:
    """Load and parse config file."""
    content = read_file(path)
    return parse_config(content)

Explicit Over Implicit

# ❌ Implicit behavior
def process(data, flag=True):
    if flag:
        return data.upper()
    return data

# ✅ Explicit, testable
def uppercase(data: str) -> str:
    """Convert data to uppercase."""
    return data.upper()

def process(data: str, transform: bool = True) -> str:
    """Process data with optional transformation.

    Args:
        data: Input data
        transform: Apply uppercase transformation

    Returns:
        Processed data
    """
    if transform:
        return uppercase(data)
    return data

Error Handling

# ✅ Good - specific exceptions
def divide(a: float, b: float) -> float:
    """Divide two numbers.

    Args:
        a: Numerator
        b: Denominator

    Returns:
        Result of division

    Raises:
        ValueError: If b is zero
    """
    if b == 0:
        raise ValueError("Cannot divide by zero")
    return a / b

# ❌ Bad - bare except
try:
    result = risky_operation()
except:  # Never do this!
    pass

# ✅ Good - specific exceptions
try:
    result = risky_operation()
except ValueError as e:
    logger.error(f"Invalid value: {e}")
    raise
except IOError as e:
    logger.error(f"IO error: {e}")
    raise

Security Guidelines

Never Commit Secrets

# ❌ Bad - hardcoded secrets
API_KEY = "sk-1234567890abcdef"
DATABASE_URL = "postgresql://user:password@localhost/db"

# ✅ Good - environment variables
import os

API_KEY = os.environ["API_KEY"]
DATABASE_URL = os.environ["DATABASE_URL"]

Input Validation

# ✅ Always validate user input
def create_user(email: str, age: int) -> User:
    """Create new user with validation.

    Args:
        email: User email address
        age: User age

    Returns:
        Created user object

    Raises:
        ValueError: If email invalid or age < 0
    """
    if not validate_email(email):
        raise ValueError(f"Invalid email: {email}")

    if age < 0:
        raise ValueError(f"Invalid age: {age}")

    return User(email=email, age=age)

SQL Injection Prevention

# ❌ Bad - SQL injection risk
def get_user(user_id: str) -> User:
    query = f"SELECT * FROM users WHERE id = {user_id}"  # Dangerous!
    return db.execute(query)

# ✅ Good - parameterized queries
def get_user(user_id: int) -> User:
    query = "SELECT * FROM users WHERE id = ?"
    return db.execute(query, (user_id,))

Naming Conventions

Variables and Functions

# snake_case for variables and functions
user_name = "John"
total_count = 100

def calculate_total_price(items: list[Item]) -> float:
    """Calculate total price."""
    ...

Classes

# PascalCase for classes
class UserValidator:
    """Validates user data."""
    ...

class EmailProcessor:
    """Processes email messages."""
    ...

Constants

# UPPER_SNAKE_CASE for constants
MAX_RETRIES = 3
DEFAULT_TIMEOUT = 30
API_BASE_URL = "https://api.example.com"

Private/Internal

class MyClass:
    """Example class with private members."""

    def __init__(self) -> None:
        """Initialize class."""
        self._internal_value = 0  # Single underscore for internal use
        self.__private_value = 0  # Double underscore for name mangling

    def public_method(self) -> int:
        """Public method."""
        return self._internal_value

    def _internal_method(self) -> None:
        """Internal method (not part of public API)."""
        ...

Testing Conventions

See @AI_DOCS/tdd-workflow.md for comprehensive testing guidelines.

Key points:

  • Test file names: test_*.py
  • Test function names: test_<description>
  • Test class names: Test<FeatureName>
  • Use fixtures in conftest.py sparingly
  • Prefer parametrized tests for multiple cases

Coverage Standards

  • Minimum: 80% (enforced)
  • Target: 90%+
  • Ideal: 100% for new code
# Check coverage
make coverage

# View HTML report
open htmlcov/index.html

Development Workflow

  1. Before coding: Run uv run ai-start-task "Task description"
  2. Write tests first: Create test_*.py with failing tests
  3. Implement code: Make tests pass
  4. Format: make format (Black + isort)
  5. Lint: make lint (Ruff + mypy + Pylint)
  6. Test: make test (pytest with coverage)
  7. Quality check: make check (all of the above)
  8. Commit: Changes pass all checks
  9. Finish: Run uv run ai-finish-task --summary="..."

Configuration Files

All tool configurations are in pyproject.toml:

[tool.black]
line-length = 88
target-version = ["py311"]

[tool.isort]
profile = "black"

[tool.ruff]
line-length = 88
select = ["E", "F", "B", "C4", "UP"]

[tool.mypy]
strict = true
python_version = "3.11"

[tool.pylint.main]
fail-under = 10.0

[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "--cov=src --cov-report=term-missing --cov-fail-under=80"

Additional Resources

  • TDD Workflow: @AI_DOCS/tdd-workflow.md
  • AI Tools: @AI_DOCS/ai-tools.md
  • Project Context: @AI_DOCS/project-context.md

Remember: Follow these conventions consistently. Run make check before committing. Quality over quantity.