Co-authored-by: Antoine <33425718+anth2o@users.noreply.github.com>
Co-authored-by: Bastien <bastien.baret@gmail.com>
Co-authored-by: Clément Sirieix <clement.sirieix@mistral.ai>
Co-authored-by: Kim-Adeline Miguel <51720070+kimadeline@users.noreply.github.com>
Co-authored-by: Mathias Gesbert <mathias.gesbert@mistral.ai>
Co-authored-by: Maxime Dolores <maxime.dolores@ext.mistral.ai>
Co-authored-by: Michel Thomazo <51709227+michelTho@users.noreply.github.com>
Co-authored-by: Nelson PROIA <144663685+Nelson-PROIA@users.noreply.github.com>
Co-authored-by: Pierre Rossinès <pierre.rossines@mistral.ai>
Co-authored-by: Quentin <quentin.torroba@mistral.ai>
Co-authored-by: Robin Gullo <robin.gullo@mistral.ai>
Co-authored-by: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
Clément Drouin 2026-04-28 17:44:07 +02:00 committed by GitHub
parent a83c81ecf5
commit 632ea8c032
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
253 changed files with 13965 additions and 2525 deletions

244
AGENTS.md
View file

@ -1,181 +1,101 @@
# python312.rule
# Rule for enforcing modern Python 3.12+ best practices.
# Applies to all Python files (*.py) in the project.
#
# Guidelines covered:
# - Use match-case syntax instead of if/elif/else for pattern matching.
# - Use the walrus operator (:=) when it simplifies assignments and tests.
# - Favor a "never nester" approach by avoiding deep nesting with early returns or guard clauses.
# - Employ modern type hints using built-in generics (list, dict) and the union pipe (|) operator,
# rather than deprecated types from the typing module (e.g., Optional, Union, Dict, List).
# - Ensure code adheres to strong static typing practices compatible with static analyzers like pyright.
# - Favor pathlib.Path methods for file system operations over older os.path functions.
# - Write code in a declarative and minimalist style that clearly expresses its intent.
# - Additional best practices including f-string formatting, comprehensions, context managers, and overall PEP 8 compliance.
# AGENTS.md
description: "Modern Python 3.12+ best practices and style guidelines for coding."
files: "**/*.py"
Conventions for AI agents and humans contributing to **Mistral Vibe** — a Python 3.12+ CLI coding assistant managed with `uv`.
guidelines:
- title: "Match-Case Syntax"
description: >
Prefer using the match-case construct over traditional if/elif/else chains when pattern matching
is applicable. This leads to clearer, more concise, and more maintainable code.
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/`.
- title: "Walrus Operator"
description: >
Utilize the walrus operator (:=) to streamline code where assignment and conditional testing can be combined.
Use it judiciously when it improves readability and reduces redundancy.
## Commands
- title: "Never Nester"
description: >
Aim to keep code flat by avoiding deep nesting. Use early returns, guard clauses, and refactoring to
minimize nested structures, making your code more readable and maintainable.
Always go through `uv` — never invoke bare `python` or `pip`.
- title: "Modern Type Hints"
description: >
Adopt modern type hinting by using built-in generics like list and dict, along with the pipe (|) operator
for union types (e.g., int | None). Avoid older, deprecated constructs such as Optional, Union, Dict, and List
from the typing module.
- `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>`.
- title: "Strong Static Typing"
description: >
Write code with explicit and robust type annotations that are fully compatible with static type checkers
like pyright. This ensures higher code reliability and easier maintenance.
## Project layout & module conventions
- title: "Pydantic-First Parsing"
description: >
Prefer Pydantic v2's native validation over ad-hoc parsing. Use `model_validate`,
`field_validator`, `from_attributes`, and field aliases to coerce external SDK/DTO objects.
Avoid manual `getattr`/`hasattr` flows and custom constructors like `from_sdk` unless they are
thin wrappers over `model_validate`. Keep normalization logic inside model validators so call sites
remain declarative and typed.
- `__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*`.
- title: "Pathlib for File Operations"
description: >
Favor the use of pathlib.Path methods for file system operations. This approach offers a more
readable, object-oriented way to handle file paths and enhances cross-platform compatibility,
reducing reliance on legacy os.path functions.
## Python style
- title: "Declarative and Minimalist Code"
description: >
Write code that is declarative—clearly expressing its intentions rather than focusing on implementation details.
Strive to keep your code minimalist by removing unnecessary complexity and boilerplate. This approach improves
readability, maintainability, and aligns with modern Python practices.
- 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
- title: "Additional Best Practices"
description: >
Embrace other modern Python idioms such as:
- Using f-strings for string formatting.
- Favoring comprehensions for building lists and dictionaries.
- Employing context managers (with statements) for resource management.
- Following PEP 8 guidelines to maintain overall code style consistency.
## Typing & imports
- title: "Exception Documentation"
description: >
Document exceptions accurately and minimally in docstrings:
- Only document exceptions that are explicitly raised in the function implementation
- Remove Raises entries for exceptions that are not directly raised
- Include all possible exceptions from explicit raise statements
- For public APIs, document exceptions from called functions if they are allowed to propagate
- Avoid documenting built-in exceptions that are obvious (like TypeError from type hints)
This ensures documentation stays accurate and maintainable, avoiding the common pitfall
of listing every possible exception that could theoretically occur.
- 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.
- title: "Modern Enum Usage"
description: >
Leverage Python's enum module effectively following modern practices:
- Use StrEnum for string-based enums that need string comparison
- Use IntEnum/IntFlag for performance-critical integer-based enums
- Use auto() for automatic value assignment to maintain clean code
- Always use UPPERCASE for enum members to avoid name clashes
- Add methods to enums when behavior needs to be associated with values
- Use @property for computed attributes rather than storing values
- For type mixing, ensure mix-in types appear before Enum in base class sequence
- Consider Flag/IntFlag for bit field operations
- Use _generate_next_value_ for custom value generation
- Implement __bool__ when enum boolean evaluation should depend on value
This promotes type-safe constants, self-documenting code, and maintainable value sets.
## Pydantic
- title: "No Inline Ignores"
description: >
Do not use inline suppressions like `# type: ignore[...]` or `# noqa[...]` in production code.
Instead, fix types and lint warnings at the source by:
- Refining signatures with generics (TypeVar), Protocols, or precise return types
- Guarding with `isinstance` checks before attribute access
- Using `typing.cast` when control flow guarantees the type
- Extracting small helpers to create clearer, typed boundaries
If a suppression is truly unavoidable at an external boundary, prefer a narrow, well-typed wrapper
over in-line ignores, and document the rationale in code comments.
- 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.
- title: "Pydantic Discriminated Unions"
description: >
When modeling variants with a discriminated union (e.g., on a `transport` field), do not narrow a
field type in a subclass (e.g., overriding `transport: Literal['http']` with `Literal['streamable-http']`).
This violates Liskov substitution and triggers type checker errors due to invariance of class attributes.
Prefer sibling classes plus a shared mixin for common fields and helpers, and compose the union with
`Annotated[Union[...], Field(discriminator='transport')]`.
Example pattern:
- Create a base with shared non-discriminator fields (e.g., `_MCPBase`).
- Create a mixin with protocol-specific fields/methods (e.g., `_MCPHttpFields`), without a `transport`.
- Define sibling final classes per variant (e.g., `MCPHttp`, `MCPStreamableHttp`, `MCPStdio`) that set
`transport: Literal[...]` once in each final class.
- Use `match` on the discriminator to narrow types at call sites.
## Async
- title: "Use uv for All Commands"
description: >
We use uv to manage our python environment. You should never try to run bare python commands.
Always run commands using `uv` instead of invoking `python` or `pip` directly.
For example, use `uv add package` and `uv run script.py` rather than `pip install package` or `python script.py`.
This practice helps avoid environment drift and leverages modern Python packaging best practices.
Useful uv commands are:
- uv add/remove <package> to manage dependencies
- uv sync to install dependencies declared in pyproject.toml and uv.lock
- uv run script.py to run a script within the uv environment
- uv run pytest (or any other python tool) to run the tool within the uv environment
- `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.
- title: "Safe File Reading"
description: >
When reading files from disk, prefer the helpers in `vibe.core.utils.io` over raw
`Path.read_text()`, `Path.read_bytes().decode()`, or `open()` calls:
- `read_safe(path)` — synchronous read with automatic encoding detection.
- `read_safe_async(path)` — async equivalent (anyio-based).
- `decode_safe(raw)` — decode an already-read `bytes` object.
These functions try UTF-8 first, then BOM detection, the locale encoding, and
`charset_normalizer` (lazily, only when cheaper candidates fail). They return a
`ReadSafeResult(text, encoding)` so callers always get valid `str` output without
having to handle encoding errors manually.
Use `raise_on_error=True` only when the caller must distinguish corrupt files from
valid ones; the default (`False`) replaces undecodable bytes with U+FFFD.
## Tools
- title: "Imports in Cursor (no Pylance)"
description: >
Cursor's built-in Pyright does not offer the "Add import" quick fix (Ctrl+.). To add a missing import:
- Use the workspace snippets: type the prefix (e.g. acpschema, acphelpers, vibetypes, vibeconfig) and accept the suggestion to insert the import line, then change the symbol name.
- Or ask Cursor: select the undefined symbol, then Cmd+K and request "Add the missing import for <symbol>".
- Or copy the import from an existing file in the repo (e.g. acp.schema, acp.helpers, vibe.core.*).
- 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.
- title: "Keep Builtin Vibe Skill Up-to-Date"
description: >
The file `vibe/core/skills/builtins/vibe.py` is the builtin self-awareness skill.
It documents the CLI's features for the model: config.toml fields, CLI parameters, slash
commands, agents, skills, tools, VIBE_HOME structure, and environment variables.
When you change any of the following, update `vibe/core/skills/builtins/vibe.py`
to reflect the new behavior:
- CLI arguments or flags (vibe/cli/entrypoint.py)
- config.toml fields or defaults (vibe/core/config/_settings.py)
- Slash commands (vibe/cli/commands.py)
- Built-in agents (vibe/core/agents/)
- VIBE_HOME directory layout or paths (vibe/core/paths/)
- Skill, tool, or agent discovery logic
- Environment variables
If in doubt, read the skill file and check whether your change makes any section stale.
## Logging & errors
- title: "No Docstrings in Tests"
description: >
Do not add docstrings to test functions, test methods, or test classes.
Test names should be descriptive enough to convey intent (e.g.,
`test_create_user_returns_403_when_unauthorized`). Docstrings in tests add
noise, duplicate the function name, and can suppress pytest's default output
(pytest displays the docstring instead of the node id when one is present).
Use inline comments sparingly for non-obvious setup or assertions instead.
- 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.