diff --git a/AGENTS.md b/AGENTS.md index d9808875..e085893d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,138 +1,216 @@ -# Contributor Quickstart Guide +# AI Agent Guide for Fromager -## Commit Message Guidelines +> **Note**: This file is also available as `CLAUDE.md` (symlink) for Claude Code CLI users. +> +> **IMPORTANT**: Before making any code changes, you MUST read [CONTRIBUTING.md](CONTRIBUTING.md) for comprehensive coding standards and design patterns. This file provides essential quick reference only. -### Objectives +## When to Read CONTRIBUTING.md -- Help the user craft commit messages that follow best practices -- Use [Conventional Commit](https://www.conventionalcommits.org/en/v1.0.0/) format unless otherwise specified -- Clarify unclear or incomplete input with targeted questions -- Ensure messages are concise, informative, and use imperative mood +**Always read CONTRIBUTING.md before:** -### Style Guidelines +- Writing new functions (for type annotation standards) +- Adding imports (for import organization rules) +- Creating tests (for testing patterns) +- Making commits (for commit message format) +- Adding error handling or logging -- Use the format: `(): ` for the subject line -- Keep the subject line ≤ 72 characters -- Use a blank line before the body -- The body explains what and why (not how) -- Use a footer for metadata (e.g., `Closes: #123`, `BREAKING CHANGE:`) +## Essential Rules (MUST FOLLOW) -### Commit Types +### Do -- **feat**: a new feature -- **fix**: a bug fix -- **docs**: documentation only changes -- **style**: formatting, missing semi colons, etc -- **refactor**: code change that neither fixes a bug nor adds a feature -- **perf**: performance improvements -- **test**: adding missing tests -- **chore**: changes to the build process or auxiliary tools +- **Type annotations REQUIRED** on ALL functions including tests. Use syntax compatible with Python 3.11+ +- Use `X | None` not `Optional[X]` +- Add docstrings on all public functions and classes +- Use file-scoped commands for fast feedback (see below) +- Follow existing patterns - search codebase for similar code +- Chain exceptions: `raise ValueError(...) from err` +- Use `req_ctxvar_context()` for per-requirement logging +- Run `hatch run lint:fix` to format code (handles line length, whitespace, etc.) -### Examples +### Don't -#### Good commit messages +- Don't use `Optional[X]` syntax (use `X | None`) +- Don't omit type annotations or return types +- Don't run full test suite for small changes (use file-scoped) +- Don't create temporary helper scripts or workarounds +- Don't commit without running quality checks +- Don't make large speculative changes without asking +- Don't update git config or force push to main +- Don't use bare `except:` - always specify exception types -```text -feat(api): add user authentication endpoint +## Commands (IMPORTANT: Use File-Scoped First) -Add JWT-based authentication system for secure API access. -Includes token generation, validation, and refresh functionality. +### File-Scoped Commands (PREFER THESE) -Closes: #123 -``` +```bash +# Type check single file +hatch run mypy:check -```text -fix(parser): handle empty input gracefully +# Format single file +hatch run lint:fix + +# Test specific file +hatch run test:test tests/test_.py -Previously, empty input would cause a null pointer exception. -Now returns an appropriate error message. +# Test specific function +hatch run test:test tests/test_.py::test_function_name + +# Debug test with verbose output +hatch run test:test --log-level DEBUG ``` -```text -docs: update installation instructions +### Project-Wide Commands (ASK BEFORE RUNNING) -Add missing dependency requirements and clarify setup steps -for new contributors. +```bash +hatch run lint:fix # Format all code +hatch run test:test # Full test suite (slow!) +hatch run mypy:check # Type check everything +hatch run lint:check # Final lint check ``` -#### Poor commit messages to avoid +## Safety and Permissions -- `fix bug` (too vague) -- `updated files` (not descriptive) -- `WIP` (not informative) -- `fixed the thing that was broken` (not professional) +### Allowed Without Asking -### Best Practices +- Read files, search codebase +- Run file-scoped linting, type checking, tests +- Edit existing files following established patterns +- Create test files -- Write in imperative mood (e.g., "add feature" not "added feature") -- Don't end the subject line with a period -- Use the body to explain the motivation for the change -- Reference issues and pull requests where relevant -- Use `BREAKING CHANGE:` in footer for breaking changes +### Ask First -## Code Quality Guidelines +- Installing/updating packages in pyproject.toml +- Git commit or push operations +- Deleting files or entire modules +- Running full test suite +- Creating new modules or major refactors +- Making breaking changes -### Formatting Standards +## Project Structure -- **No trailing whitespace**: Ensure no extra spaces at the end of lines -- **No whitespace on blank lines**: Empty lines should contain no spaces or tabs -- **End files with a single newline**: Each file should end with a single newline character (`\n`). This is a widely adopted convention recommended by PEP 8, Python's style guide. Many Unix-style text editors and tools expect files to end with a newline character and may not handle files without one properly. -- Follow the project's existing code style and indentation patterns -- Use consistent line endings (LF for this project) +- `src/fromager/` - Main package code +- `tests/` - Unit tests (mirror `src/` structure) +- `e2e/` - End-to-end integration tests +- `docs/` - Sphinx documentation -### Type Annotations +### Reference Files for Patterns -- **Always add type annotations to all functions**: All functions must include type annotations for parameters and return values -- This applies to regular functions, test functions, class methods, and async functions -- Existing code will be updated gradually; new code must be fully typed -- Follow the existing pattern in the codebase for consistency -- Examples: +**Before writing code, look at these examples:** - ```python - def calculate_total(items: list[int]) -> int: - """Calculate sum of items.""" - return sum(items) +- Type annotations: `src/fromager/context.py` +- Pydantic models: `src/fromager/packagesettings.py` +- Logging with context: `src/fromager/resolver.py` +- Error handling: `src/fromager/commands.py` +- Testing patterns: `tests/test_context.py` - def test_my_feature() -> None: - """Test that my feature works correctly.""" - assert my_feature() == expected_result - ``` +## Code Patterns -### Code Comments +**Import Guidelines:** -- **Avoid unnecessary comments**: Write self-documenting code with clear variable names, function names, and structure -- **Only add comments when absolutely necessary**: Comments should be reserved for must-have cases such as: - - Explaining complex algorithms or non-obvious logic - - Documenting "why" decisions were made (not "what" the code does) - - Warning about edge cases or subtle bugs - - Explaining workarounds for external library issues -- **Do not add comments that simply restate what the code does**: The code itself should be clear enough -- **Prefer docstrings over comments**: Use docstrings for functions, classes, and modules to document their purpose and usage +- **PEP 8: imports should be at the top**: All import statements must be placed at the top of the file, after module docstrings and before other code +- **No local imports**: Do not place import statements inside functions, methods, or conditional blocks -### Testing After Code Changes +### Testing Pattern -After making code changes, run the following tests within a Python virtual environment to ensure code quality: +```python +def test_behavior(tmp_path: pathlib.Path) -> None: + """Verify expected behavior.""" + # Arrange + config = tmp_path / "config.txt" + config.write_text("key=value\n") + # Act + result = load_config(config) + # Assert + assert result["key"] == "value" +``` -#### Run all unit tests +## Commit Message Format (REQUIRED) -```bash -hatch run test:test +Use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) format: + +```text +(): + + + + ``` -#### Run lint checks +### Types -```bash -hatch run lint:check +- **feat**: new functionality +- **fix**: bug fix +- **docs**: documentation only +- **test**: tests only +- **refactor**: behavioral no-op refactor +- **perf**: performance improvement +- **chore**: tooling or dependency change + +### Good Examples + +```text +feat(resolver): add exponential backoff for HTTP retries + +Improves resilience when PyPI is under load by adding jittered backoff. + +Closes: #123 ``` -#### Run mypy type checking +```text +fix(constraints): handle missing constraint file gracefully + +Validate file existence and emit helpful message instead of crashing. +``` -```bash -hatch run mypy:check +### AI Agent Attribution + +When AI agents create or significantly modify code, add attribution using `Co-Authored-By`: + +```text +feat(resolver): add exponential backoff for HTTP retries + +Improves resilience when PyPI is under load by adding jittered backoff. + +Co-Authored-By: Claude +Closes: #123 ``` -### Before Committing +### Bad Examples (NEVER DO THIS) + +- `fix bug` (too vague) +- `updated files` (not descriptive) +- `WIP` (not informative) +- `fixed the thing that was broken` (not professional) + +## Workflow for Complex Tasks + +1. **Search codebase** for similar patterns first +2. **Create a checklist** in a markdown file for tracking +3. **Work through items systematically** one at a time +4. **Run file-scoped tests** after each change +5. **Check off completed items** before moving to next +6. **Run full quality checks** only at the end + +## When Uncertain + +- Ask clarifying questions rather than making assumptions +- Search the codebase for similar patterns before inventing new ones +- Propose a specific plan before making large changes +- Reference [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidance +- **DO NOT** make large speculative changes without confirmation + +## Quality Checklist Before Finishing + +- [ ] Read CONTRIBUTING.md for relevant standards +- [ ] Type annotations on all functions +- [ ] Docstrings on public APIs +- [ ] Tests cover the change +- [ ] File-scoped tests pass +- [ ] No trailing whitespace +- [ ] File ends with single newline +- [ ] Conventional Commit format used +- [ ] Full quality checks pass: `hatch run lint:fix && hatch run test:test && hatch run mypy:check && hatch run lint:check` + +--- -- Review your changes for trailing whitespace: `git diff | grep -E "^\+.*[[:space:]]$"` -- Run tests to ensure all changes work correctly -- Check for linting errors if the project uses linters +**See [CONTRIBUTING.md](CONTRIBUTING.md) for comprehensive standards, detailed examples, and design patterns used in Fromager.** diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..4179b703 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,376 @@ +# Contributing to Fromager + +Fromager thrives on practical, well-tested contributions. This guide summarizes how to set up a workspace, follow our standards, and submit polished changes. Skim it once, keep it handy, and refer back whenever you are unsure. + +> **Note**: If you're using AI coding assistants, also see [AGENTS.md](AGENTS.md) for AI-optimized quick reference. + +## Table of Contents + +- [Quick Start](#quick-start) +- [Coding Standards](#coding-standards) +- [Testing](#testing) +- [Commit Guidelines](#commit-guidelines) +- [Before Submitting](#before-submitting) +- [Design Patterns Used in Fromager](#design-patterns-used-in-fromager) +- [Quick Reference](#quick-reference) +- [Getting Help](#getting-help) +- [Resources](#resources) + +--- + +## Quick Start + +### Prerequisites + +- Python 3.11 or newer +- `hatch` for environment and task management + + ```bash + pip install hatch + # or + pipx install hatch # recommended + ``` + +### Initial Setup + +```bash +# 1. Fork the repository on GitHub +# 2. Clone your fork +git clone https://github.com//fromager.git +cd fromager + +# 3. Add upstream remote +git remote add upstream https://github.com/python-wheel-build/fromager.git + +# 4. Create development environment +hatch env create +``` + +### Contribution Workflow + +```bash +# 1. Sync with upstream +git checkout main +git fetch upstream +git merge upstream/main + +# 2. Create a feature branch +git checkout -b feat/ + +# 3. Make changes and test as you go +hatch run test:test tests/test_.py # Test your specific changes + +# 4. Before committing, run full quality checks +hatch run lint:fix && hatch run test:test && hatch run mypy:check && hatch run lint:check + +# 5. Commit using Conventional Commits +git commit -m "feat(scope): short summary" + +# 6. Push to your fork +git push origin feat/ + +# 7. Create a pull request on GitHub +``` + +--- + +## Coding Standards + +### Type Annotations + +- Every function (including tests) must annotate all parameters and return values. +- Use modern `X | None` syntax instead of `Optional[X]` (requires Python 3.11+). +- Prefer precise collection types (`list[str]`, `dict[str, int]`, etc.). + +```python +def process(data: str | None, count: int = 0) -> dict[str, int]: + """Process the input and return aggregate counts.""" + return {} +``` + +### Code Quality and Formatting + +- Ruff enforces both formatting and linting. +- Run `hatch run lint:fix` to automatically format code. +- See [Quick Reference](#quick-reference) for additional commands. + +### Import Organization + +- **PEP 8: imports should be at the top**: All import statements must be placed at the top of the file, after module docstrings and before other code. +- **No local imports**: Do not place import statements inside functions, methods, or conditional blocks. + +### Documentation Expectations + +- Add a module docstring describing purpose and high-level behavior. +- Public functions and classes require docstrings that cover arguments, return values, and noteworthy behavior. +- Keep prose short and imperative; explain "why" decisions when the code itself cannot. + +```python +def retry_on_exception( + exceptions: tuple[type[Exception], ...], + max_attempts: int = 5, +) -> typing.Callable: + """Retry decorated call on the provided exception types. + + Args: + exceptions: Exception types that trigger a retry. + max_attempts: Maximum number of attempts. + + Returns: + Decorator function. + """ + ... +``` + +### Commenting Guidelines + +- Write self-explanatory code; reserve comments for non-obvious intent or domain context. +- Capture reasoning, invariants, or unexpected trade-offs—never repeat the code literally. + +```python +# Bad - comment just repeats the code +x = x + 1 # increment x + +# Good - clear variable name makes the comment unnecessary +total_attempts += 1 + +# Good - comment explains the reasoning behind the approach +# Exponential backoff avoids thundering herd when many jobs retry at once. +wait_time = min(2**attempt + random.uniform(0, 1), max_backoff) +``` + +### Logging + +- Use a module-level logger and the appropriate log level for the situation. +- When processing per-requirement work, wrap nested calls in `req_ctxvar_context()` so log records automatically include the package (and optional version). This keeps CLI logs searchable even when work runs in parallel. + +```python +logger = logging.getLogger(__name__) + +def sync_artifacts() -> None: + logger.info("Starting artifact sync") + logger.debug("Artifacts queued: %s", pending_jobs) + +# When processing a specific package, wrap calls to include package info in logs +# req: Requirement object for the package being processed +# version: Version string being built (optional) +with req_ctxvar_context(req, version): + logger.info("Resolving build dependencies") # Logs will include package name +``` + +### Error Handling + +- Raise specific exceptions with actionable messages. +- Chain exceptions (`raise ... from e`) so stack traces stay informative. + +```python +try: + result = process_data(file_path) +except FileNotFoundError as err: + raise ValueError(f"Cannot load config at {file_path}") from err +``` + +--- + +## Testing + +### Structure + +- Place tests under `tests/`. +- Name files `test_.py` and functions `test_()`. +- Keep tests small: arrange, act, assert. + +```python +def test_load_config(tmp_path: pathlib.Path) -> None: + """Verify config loads with expected values.""" + # Arrange + config_file = tmp_path / "config.txt" + config_file.write_text("setting=value\n") + # Act + result = load_config(config_file) + # Assert + assert result["setting"] == "value" +``` + +### Useful Commands + +```bash +hatch run test:test # Full suite +hatch run test:test tests/test_context.py # Specific file +hatch run test:test --log-level DEBUG # Verbose output +hatch run test:coverage-report # Coverage summary +``` + +--- + +## Commit Guidelines + +Fromager follows [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/). + +```text +(): + + + +