# Code Style — grafeo-mcp

## Formatting

- **Line length:** 120 characters
- **Formatter:** ruff format (black-compatible)
- **Import sorting:** ruff (isort-compatible, rule `I`)
- **Target:** Python 3.12+

## Ruff Rules

```toml
select = ["E", "F", "W", "I", "UP", "B", "SIM", "RUF"]
```

- **E/W/F** — pycodestyle + pyflakes basics
- **I** — isort import ordering
- **UP** — pyupgrade (use modern Python syntax)
- **B** — bugbear (common pitfalls)
- **SIM** — simplify (reduce unnecessary complexity)
- **RUF** — ruff-specific rules

## Naming

- **Modules:** snake_case (`query.py`, `vector.py`)
- **Functions/tools:** snake_case (`execute_gql`, `create_node`)
- **Classes:** PascalCase (`AppContext`, `MCPServer`)
- **Constants:** UPPER_SNAKE_CASE (`GRAFEO_DB_PATH`)
- **Private helpers:** prefix with `_`
- **Type aliases:** PascalCase

## Imports

Standard ordering (enforced by ruff `I`):

```python
from __future__ import annotations          # 1. future (always)

import os                                    # 2. stdlib
from collections.abc import AsyncIterator

import grafeo                                # 3. third-party
from mcp.server.fastmcp import Context, FastMCP

from grafeo_mcp.server import mcp            # 4. local
```

## Type Annotations

- `from __future__ import annotations` at the top of every module.
- Use modern syntax: `list[str]`, `dict[str, Any]`, `X | None` (not `Optional[X]`).
- Type hints on public APIs; skip for internal helpers.
- Keyword-only for optional parameters (use `*`).
- No `# type: ignore` — fix the actual types.
- Type checker: `ty` (Astral).

## Docstrings

- One-line summary, extended description if needed.
- Skip for internal helpers and trivial/obvious methods.
- Minimal comments — explain *why*, not *what*.

## Tool Functions

```python
@mcp.tool()
def tool_name(
    required_param: str,
    optional_param: int = 10,
    ctx: Context[ServerSession, AppContext] = None,
) -> ReturnType:
    """One-line summary for the agent.

    Longer description with usage guidance.
    """
    db = ctx.request_context.lifespan_context.db
    # ... implementation
```

- Docstrings are agent-facing — write them as clear instructions with examples.
- Keep parameters flat (no nested dicts).
- All list-returning tools must accept a `limit` parameter.
- Return error strings instead of raising exceptions.
- Error messages should suggest which tool to try next (recovery hints).

### Tool Docstring Format

```text
1. One-line summary
2. Detailed description (2-3 sentences)
3. "Use this tool when: ..." line
4. "Do NOT use for: ... (use X instead)" line
5. Args section with clear descriptions
6. Example usage if helpful
```

## Data Structures & Patterns

- **snake_case everywhere** — functions, variables, parameters.
- **Early returns** — exit functions as soon as possible.
- **Sets for membership** — O(1) lookup, not lists.
- **Frozen dataclasses** with `slots=True` for immutable records.
- **Walrus operator** for first-match patterns.

## Error Handling

- Return error strings from tools instead of raising exceptions.
- Let exceptions propagate naturally in non-tool code; don't catch-and-silence.
- Validate at boundaries (public method inputs), trust internals.

## Tests

- Framework: pytest + pytest-asyncio
- Async mode: `auto` (no need for `@pytest.mark.asyncio`)
- Test files mirror source: `test_tools_query.py` tests `tools/query.py`
- Use fixtures in `conftest.py` for shared GrafeoDB instances.
- Test names: `test_<function>_<scenario>`
