diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..7b77f5e75 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,233 @@ +# AGENTS.md -- crewAI Repository Contributor Guide for AI Coding Assistants + +> Repo-local instructions for AI coding assistants (Claude Code, Cursor, +> Windsurf, GitHub Copilot, Devin, Codex, etc.) contributing to the **crewAI +> monorepo**. Follow these deterministic validation steps before opening or +> updating any pull request. + +--- + +## Repository Layout + +``` +crewAI/ # workspace root (uv workspace) + pyproject.toml # workspace-level config (ruff, mypy, pytest, uv) + conftest.py # shared pytest fixtures (VCR, event cleanup, env) + uv.lock + lib/ + crewai/ # core framework package + src/crewai/ # source code + tests/ # unit & integration tests + pyproject.toml + crewai-tools/ # tool integrations package + src/crewai_tools/ # source code + tests/ # unit & integration tests + pyproject.toml + crewai-files/ # file-processing package + src/crewai_files/ + tests/ + pyproject.toml + devtools/ # internal developer tooling (lib/devtools) + src/ + pyproject.toml + docs/ # documentation site content + .github/workflows/ # CI definitions (lint, tests, type-check) +``` + +Key conventions: +- **uv workspace** -- all packages are managed via `uv` with workspace members + declared in the root `pyproject.toml`. +- Source lives under `lib//src/`, tests under `lib//tests/`. +- Shared test configuration (fixtures, VCR cassette filtering) is in the + root `conftest.py`. + +--- + +## Environment Setup + +```bash +# 1. Install uv (if not already installed) +curl -LsSf https://astral.sh/uv/install.sh | sh + +# 2. Install all workspace dependencies (including dev tools) +uv sync --all-groups --all-extras + +# 3. Verify the virtual environment +uv run python -c "import crewai; print(crewai.__version__)" +``` + +Python version requirement: `>=3.10, <3.14` (see root `pyproject.toml`). + +--- + +## Deterministic Validation Workflow + +Run these checks **in order** before pushing commits. Every command must exit +with code 0. + +### 1. Lint (ruff) + +```bash +# Check for lint errors on changed files (mirrors CI) +uv run ruff check --config pyproject.toml + +# Or check all source files +uv run ruff check --config pyproject.toml lib/ +``` + +The CI job runs ruff only on changed Python files, excluding +`lib/crewai/src/crewai/cli/templates/` and `*/tests/` directories. + +Configuration: root `pyproject.toml` `[tool.ruff]` section. + +### 2. Format (ruff) + +```bash +# Check formatting (dry-run) +uv run ruff format --check --config pyproject.toml lib/ + +# Auto-fix formatting +uv run ruff format --config pyproject.toml +``` + +### 3. Type Check (mypy) + +```bash +# Run mypy on changed source files (excluding tests and templates) +uv run mypy --config-file pyproject.toml +``` + +CI runs mypy only on changed files under `lib/*/src/` (not tests). +Configuration: root `pyproject.toml` `[tool.mypy]` section with +`strict = true`. + +### 4. Tests (pytest) + +```bash +# Run the full crewai test suite +cd lib/crewai && uv run pytest -vv --maxfail=3 + +# Run the crewai-tools test suite +cd lib/crewai-tools && uv run pytest -vv --maxfail=3 + +# Run the crewai-files test suite +cd lib/crewai-files && uv run pytest -vv --maxfail=3 + +# Run a single test file +cd lib/crewai && uv run pytest tests/path/to/test_file.py -vv + +# Run a single test +cd lib/crewai && uv run pytest tests/path/to/test_file.py::test_function -vv +``` + +Test configuration (root `pyproject.toml` `[tool.pytest.ini_options]`): +- `asyncio_mode = "strict"` -- async tests require explicit + `@pytest.mark.asyncio`. +- `--block-network` -- network calls are blocked by default; use VCR + cassettes for HTTP interactions. +- `--timeout=60` -- per-test timeout of 60 seconds. +- `-n auto` -- parallel execution via pytest-xdist. +- `--dist=loadfile` -- tests from the same file run in the same worker. + +### 5. Pre-commit (optional local check) + +```bash +uv run pre-commit run --all-files +``` + +Runs ruff lint, ruff format, mypy, uv-lock consistency, and commitizen +checks. + +--- + +## CI Workflows (GitHub Actions) + +Pull requests trigger these workflows automatically: + +| Workflow | File | What it checks | +|----------|------|----------------| +| **Lint** | `.github/workflows/linter.yml` | `ruff check` on changed `.py` files | +| **Tests** | `.github/workflows/tests.yml` | `pytest` across Python 3.10--3.13, split into 8 groups | +| **Type Check** | `.github/workflows/type-checker.yml` | `mypy` on changed source files across Python 3.10--3.13 | + +All three must pass before merge. + +--- + +## Safety Boundaries + +- **Never commit secrets.** API keys, tokens, and credentials belong in + `.env` (git-ignored) or environment variables. The `.env.test` file + provides safe placeholder values for the test suite. +- **Never modify VCR cassettes by hand.** Cassettes are auto-generated YAML + recordings of HTTP interactions. To re-record, set + `PYTEST_VCR_RECORD_MODE=all` and run the relevant tests. Cassette paths + mirror the test directory structure under `tests/cassettes/`. +- **Never modify tests just to make them pass.** If a test fails, fix the + source code or report the issue. Do not weaken assertions, add + `pytest.skip`, or catch exceptions to hide failures. +- **Respect `--block-network`.** Tests run with network blocking enabled. + All external HTTP calls must use VCR cassettes. Do not disable this. +- **Do not add new dependencies without justification.** The workspace pins + dependencies tightly. Any new dependency needs explicit rationale and must + be added via `uv add`, not by editing `pyproject.toml` by hand. +- **Do not push directly to `main`.** All changes go through pull requests. + +--- + +## Commit Convention + +This repository uses [Commitizen](https://commitizen-tools.github.io/commitizen/) +with [Conventional Commits](https://www.conventionalcommits.org/): + +``` +(): + +# Examples: +feat(agent): add inject_date parameter +fix(llm): handle empty response from Bedrock +docs: update AGENTS.md validation commands +test(crew): add hierarchical process edge case +refactor(flow): simplify state persistence logic +``` + +Valid types: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, +`build`, `ci`, `chore`, `revert`. + +The `commitizen` pre-commit hook validates commit messages on push. + +--- + +## Testing Conventions + +- **Test file naming:** `test_*.py` in the appropriate `tests/` directory. +- **Test function naming:** `test_*` prefix. +- **Test class naming:** `Test*` prefix. +- **Fixtures:** Shared fixtures live in the root `conftest.py`. Package- + specific fixtures go in `lib//tests/conftest.py`. +- **VCR cassettes:** Use `@pytest.mark.vcr` for tests that make HTTP calls. + Cassettes are stored in `tests/cassettes/` mirroring the test directory + structure. +- **Async tests:** Must be decorated with `@pytest.mark.asyncio` (strict + mode). +- **Telemetry tests:** Mark with `@pytest.mark.telemetry` to opt out of + the default telemetry mocking. + +--- + +## Code Style + +- **Linter/formatter:** ruff (config in root `pyproject.toml`). +- **Type checker:** mypy with `strict = true`. +- **Imports:** Absolute imports only (`ban-relative-imports = "all"`). + Sorted by isort rules. No unused imports. +- **Line length:** No hard limit enforced (`E501` ignored), but keep lines + reasonable. +- **Print statements:** Disallowed in source code (ruff rule `T`). Use + proper logging. +- **Security:** Bandit rules enforced (ruff rule `S`). `assert` statements + are allowed in test files only. +- **Type annotations:** Required on all function signatures in source code + (mypy `disallow_untyped_defs = true`). Use `X | Y` union syntax (not + `Optional` or `Union`), `collections.abc` types, and built-in generics + (`list`, `dict` instead of `typing.List`, `typing.Dict`). diff --git a/lib/crewai/tests/test_agents_md.py b/lib/crewai/tests/test_agents_md.py new file mode 100644 index 000000000..c4cfa94d1 --- /dev/null +++ b/lib/crewai/tests/test_agents_md.py @@ -0,0 +1,129 @@ +"""Regression tests for the repo-local AGENTS.md file. + +Validates that AGENTS.md exists at the repository root and contains the +required sections and validation commands so that contributors and +automation agents have deterministic, machine-readable guidance. + +Addresses: https://github.com/crewAIInc/crewAI/issues/4564 +""" + +from pathlib import Path + +import pytest + + +REPO_ROOT = Path(__file__).resolve().parents[3] +AGENTS_MD = REPO_ROOT / "AGENTS.md" + + +@pytest.fixture() +def agents_md_content() -> str: + """Read and return the full text of the repo-root AGENTS.md.""" + assert AGENTS_MD.is_file(), ( + f"AGENTS.md not found at repository root ({REPO_ROOT}). " + "See https://github.com/crewAIInc/crewAI/issues/4564" + ) + return AGENTS_MD.read_text(encoding="utf-8") + + +class TestAgentsMdExists: + """AGENTS.md must be present at the repository root.""" + + def test_agents_md_file_exists(self) -> None: + assert AGENTS_MD.is_file(), ( + f"Expected AGENTS.md at {REPO_ROOT}. " + "This file is required for contributor and automation-agent guidance." + ) + + def test_agents_md_is_not_empty(self, agents_md_content: str) -> None: + assert len(agents_md_content.strip()) > 0, "AGENTS.md must not be empty." + + +class TestAgentsMdRequiredSections: + """AGENTS.md must contain sections covering validation, safety, and workflow.""" + + REQUIRED_HEADINGS = [ + "Repository Layout", + "Environment Setup", + "Deterministic Validation Workflow", + "Safety Boundaries", + "Commit Convention", + "Testing Conventions", + "Code Style", + "CI Workflows", + ] + + @pytest.mark.parametrize("heading", REQUIRED_HEADINGS) + def test_required_section_present( + self, + agents_md_content: str, + heading: str, + ) -> None: + assert heading in agents_md_content, ( + f"AGENTS.md is missing required section: '{heading}'" + ) + + +class TestAgentsMdValidationCommands: + """AGENTS.md must document the exact validation commands used in CI.""" + + REQUIRED_COMMANDS = [ + "uv run ruff check", + "uv run ruff format", + "uv run mypy", + "uv run pytest", + ] + + @pytest.mark.parametrize("command", REQUIRED_COMMANDS) + def test_validation_command_documented( + self, + agents_md_content: str, + command: str, + ) -> None: + assert command in agents_md_content, ( + f"AGENTS.md must document the validation command: '{command}'" + ) + + +class TestAgentsMdSafetyRules: + """AGENTS.md must include key safety rules.""" + + SAFETY_KEYWORDS = [ + "Never commit secrets", + "Never modify VCR cassettes by hand", + "Never modify tests just to make them pass", + "--block-network", + ] + + @pytest.mark.parametrize("keyword", SAFETY_KEYWORDS) + def test_safety_rule_present( + self, + agents_md_content: str, + keyword: str, + ) -> None: + assert keyword in agents_md_content, ( + f"AGENTS.md is missing safety rule containing: '{keyword}'" + ) + + +class TestAgentsMdWorkspaceStructure: + """AGENTS.md must accurately describe the workspace layout.""" + + WORKSPACE_MEMBERS = [ + "lib/crewai/", + "lib/crewai-tools/", + "lib/crewai-files/", + "lib/devtools/", + ] + + @pytest.mark.parametrize("member", WORKSPACE_MEMBERS) + def test_workspace_member_documented( + self, + agents_md_content: str, + member: str, + ) -> None: + # Normalize: the file may reference paths with or without trailing / + member_stem = member.rstrip("/") + assert member_stem in agents_md_content, ( + f"AGENTS.md must document workspace member: '{member_stem}'" + )