Co-authored-by: Clément Drouin <clement.drouin@mistral.ai> Co-authored-by: Gauthier Guinet <43207538+Gguinet@users.noreply.github.com> Co-authored-by: Michel Thomazo <51709227+michelTho@users.noreply.github.com> Co-authored-by: Pierre Rossinès <pierre.rossines@mistral.ai> Co-authored-by: Stanislas <stanislas.lange@mistral.ai> Co-authored-by: Mistral Vibe <vibe@mistral.ai>
107 lines
7.2 KiB
Markdown
107 lines
7.2 KiB
Markdown
# 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 <pkg>`, `uv remove <pkg>`.
|
|
|
|
## 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 its class in production code. Accessing private methods in tests is acceptable.
|
|
- Avoid comments and docstrings, except for when there's a hard to spot corner case
|
|
|
|
## 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`), `LOG_MAX_BYTES`. Logs land in `~/.vibe/logs/vibe.log`.
|
|
- Pass variables as `%s` positional args, not f-string interpolation: prefer `logger.error("Failed to fetch url=%s", url)` over `logger.error(f"Failed to fetch {url}")`. This defers formatting to the logging framework (only formats if the message is emitted) and keeps messages grep-friendly.
|
|
- 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.
|
|
|
|
## TCSS
|
|
|
|
- When a rule sets `color: $text-muted;`, pair it with a nested `&:ansi { text-style: dim; }` so the muted intent survives under ANSI themes.
|
|
- Never use `ansi_*` colors (e.g. `ansi_red`, `ansi_bright_blue`). Use Textual theme variables like `$primary`, `$foreground`, `$surface`, `$error`, etc. — see https://textual.textualize.io/guide/design/. ANSI themes are derived from these variables automatically.
|
|
|
|
## 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.
|