# AGENTS.md Conventions for AI agents and humans contributing to **Mistral Vibe** — a Python 3.12+ CLI coding assistant managed with `uv`. Layout: `vibe/core` is the engine (agent loop, tools, LLM backends, config); `vibe/cli` is the Textual TUI; `vibe/acp` bridges to the Agent Client Protocol; `vibe/setup` runs first-run wizards. Tests live in `tests/` with autouse fixtures in `conftest.py` and test doubles in `tests/stubs/`. ## Commands Always go through `uv` — never invoke bare `python` or `pip`. - `uv run vibe` / `uv run vibe-acp` — the two entry points. - `uv run pytest` — full suite (parallel via `pytest-xdist`). - `uv run pyright` — strict type check. - `uv run ruff check --fix .` and `uv run ruff format .` — run both after every code change and report the files modified. - `uv run pre-commit run --all-files` — full lint pass. Install once with `uv tool install pre-commit && uv run pre-commit install`. - Useful uv basics: `uv sync --all-extras`, `uv add `, `uv remove `. ## Project layout & module conventions - `__init__.py` exposes the public API via an explicit `__all__`. - Private modules are prefixed with `_` (e.g. `_settings.py`, `_config.py`). - Pydantic models live in `models.py`; configuration in `_settings.py` / `_config.py`. - Abstract interfaces use the `_port.py` suffix (hexagonal-style ports). - Tests mirror the source layout; test doubles in `tests/stubs/` are named `Fake*`. ## Python style - Prefer `match` / `case` over long `if` / `elif` chains. - Use the walrus operator `:=` only when it shortens code and improves clarity. - Be a never-nester: early returns and guard clauses over nested blocks. - Modern type hints only: built-in generics (`list`, `dict`) and `|` unions. Never import `Optional`, `Union`, `Dict`, `List` from `typing`. - Use `pathlib.Path` (and `anyio.Path` in async paths) instead of `os.path`. - Use f-strings, comprehensions, and context managers; follow PEP 8. - Enums: `StrEnum` / `IntEnum` with `auto()` and UPPERCASE members. For type-mixing, the mix-in type comes before `Enum` in the bases. Add methods or `@property` rather than parallel lookup tables. - Write declarative, minimalist code: express intent, drop boilerplate. - Never call a private method from outside of it's class ## Typing & imports - Pyright is strict and gates CI; fix types at the source. - No relative imports — `ban-relative-imports = "all"`. Always `from vibe.core.x import …`. - No inline `# type: ignore` or `# noqa`. Fix with refined signatures (TypeVar, Protocol), `isinstance` guards, `typing.cast` when control flow guarantees the type, or a small typed wrapper at the boundary. ## Pydantic - Parse external data via `model_validate`, `field_validator`, or `model_validator(mode="before")` — never ad-hoc `getattr` / `hasattr` walks or custom `from_sdk` constructors. - Set `ConfigDict(extra=…)` explicitly. Use `validation_alias` (or field aliases) for kebab-case TOML keys. - Discriminated unions (e.g. MCP `transport`): use sibling final classes plus a shared base/mixin, and compose with `Annotated[Union[...], Field(discriminator=...)]`. Never narrow the discriminator field in a subclass — it violates LSP and pyright will reject it. - Document `Raises:` only for exceptions the function actually raises (or that propagate from public API calls). Don't list speculative built-ins. ## Async - `asyncio` is the orchestration runtime in the agent loop and tool execution. Use `asyncio.create_task` + queues for concurrent work, not blanket `gather`. - Use `anyio.Path` for file I/O on async paths. - Streaming surfaces return `AsyncGenerator[Event, None]`, not coroutines. - HTTP via `httpx.AsyncClient`; mock with `respx` in tests. ## Tools - Subclass `BaseTool` from `vibe/core/tools/base.py` with a Pydantic args model and a `BaseToolConfig` generic parameter. - Implement `async def run(args, ctx: InvokeContext)` and yield events progressively. - Raise `ToolError` for user-facing failures; raise `ToolPermissionError` for authorization failures. - Declare permission with `ToolPermission` (`ALWAYS` / `ASK` / `NEVER`); honor it consistently. ## Logging & errors - Use `from vibe.core.logger import logger` — stdlib `logging` with `StructuredLogFormatter`, not `structlog`. - Configure via env: `LOG_LEVEL` (default `WARNING`), `DEBUG_MODE`, `LOG_MAX_BYTES`. Logs land in `~/.vibe/logs/vibe.log`. - Pass variables as keyword args, not interpolated into the message: prefer `logger.error("Failed to fetch", url=url)` over `logger.error(f"Failed to fetch {url}")`. - Define module-local exception hierarchies. Always chain with `raise NewError(...) from e`. Rich exceptions expose a `_fmt()` helper for human-readable output. ## File I/O - Prefer `vibe.core.utils.io.read_safe` / `read_safe_async` / `decode_safe` over raw `Path.read_text()`, `Path.read_bytes().decode()`, or `open()`. - They return `ReadSafeResult(text, encoding)` and try UTF-8, then BOM detection, then locale, then `charset_normalizer` lazily. - Pass `raise_on_error=True` only when callers must distinguish corrupt files from valid ones; the default replaces undecodable bytes with U+FFFD. ## Tests - Stack: `pytest` + `pytest-asyncio` + `pytest-textual-snapshot` + `respx`. - Mark async tests with `@pytest.mark.asyncio`. Mock outbound HTTP with `respx`. - Rely on the autouse fixtures in `tests/conftest.py` (`config_dir`, `tmp_working_directory`) for filesystem and home-dir isolation. - No docstrings on test functions, methods, or classes — descriptive names like `test_create_user_returns_403_when_unauthorized` carry the intent. Pytest displays docstrings instead of node IDs when present, which hurts. - Tests are exempt from the `ANN` and `PLR` ruff rules (see `per-file-ignores`). ## Git - Never use `git commit --amend`, `git push --force`, or `git push --force-with-lease`. - Always create new commits and push with a plain `git push`. - If a push is rejected due to upstream changes, rebase onto the updated remote branch — never merge and never force-push. ## Editor tip In Cursor / Pyright, the "Add import" quick fix is missing — use the workspace snippets `acpschema`, `acphelpers`, `vibetypes`, `vibeconfig` to insert the import line, then rename the symbol. ## Autoimprovement - Suggest to add new rules to AGENTS.md based on user input or PR comments, when a change request could be generalized as a rule. - Suggest updates to the README.md file according to feature changes or additions - Keep the builtin Vibe Skill (`vibe/core/skills/builtins/vibe.py`) up-to-date. It documents the CLI's features, such as args, flags, config options and persistence, commands, built-in agents, file discovery logic.