Add AGENTS.md with coding guidelines for AI agents
Comprehensive guide covering: - Build/lint/test commands including single test execution - Code style: imports, formatting, types, naming, error handling - Discord patterns: @logged_command, autocomplete, embed emojis - Service layer abstraction rules - Model patterns (from_api_data, required IDs) - Testing with aioresponses and complete model data - Critical rules for git, services, and commits 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
2881207ca4
commit
d6ec2a11ec
190
AGENTS.md
Normal file
190
AGENTS.md
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
# AGENTS.md - Discord Bot v2.0
|
||||||
|
|
||||||
|
Guidelines for AI coding agents working in this repository.
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
**Start bot**: `python bot.py`
|
||||||
|
**Run all tests**: `python -m pytest --tb=short -q`
|
||||||
|
**Run single test file**: `python -m pytest tests/test_models.py -v`
|
||||||
|
**Run single test**: `python -m pytest tests/test_models.py::TestTeamModel::test_team_creation_minimal -v`
|
||||||
|
**Run tests matching pattern**: `python -m pytest -k "test_player" -v`
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
- `bot.py` - Main entry point
|
||||||
|
- `commands/` - Discord slash commands (package-based)
|
||||||
|
- `services/` - API service layer (BaseService pattern)
|
||||||
|
- `models/` - Pydantic data models
|
||||||
|
- `views/` - Discord UI components (embeds, modals)
|
||||||
|
- `utils/` - Logging, decorators, caching
|
||||||
|
- `tests/` - pytest test suite
|
||||||
|
|
||||||
|
## Code Style
|
||||||
|
|
||||||
|
### Imports
|
||||||
|
Order: stdlib, third-party, local. Separate groups with blank lines.
|
||||||
|
|
||||||
|
```python
|
||||||
|
import asyncio
|
||||||
|
from typing import Optional, List
|
||||||
|
|
||||||
|
import discord
|
||||||
|
from discord.ext import commands
|
||||||
|
|
||||||
|
from services.player_service import player_service
|
||||||
|
from utils.decorators import logged_command
|
||||||
|
```
|
||||||
|
|
||||||
|
### Formatting
|
||||||
|
- Line length: 100 characters max
|
||||||
|
- Docstrings: Google style with triple quotes
|
||||||
|
- Indentation: 4 spaces
|
||||||
|
- Trailing commas in multi-line structures
|
||||||
|
|
||||||
|
### Type Hints
|
||||||
|
Always use type hints for function signatures:
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def get_player(self, player_id: int) -> Optional[Player]:
|
||||||
|
async def search_players(self, query: str, limit: int = 10) -> List[Player]:
|
||||||
|
```
|
||||||
|
|
||||||
|
### Naming Conventions
|
||||||
|
- Classes: `PascalCase` (PlayerService, TeamInfoCommands)
|
||||||
|
- Functions/methods: `snake_case` (get_player, search_players)
|
||||||
|
- Constants: `UPPER_SNAKE_CASE` (SBA_CURRENT_SEASON)
|
||||||
|
- Private: prefix with `_` (_client, _team_service)
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
Use custom exceptions from `exceptions.py`. Prefer "raise or return" over Optional:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from exceptions import APIException, PlayerNotFoundError
|
||||||
|
|
||||||
|
async def get_player(self, player_id: int) -> Player:
|
||||||
|
result = await self.get_by_id(player_id)
|
||||||
|
if result is None:
|
||||||
|
raise PlayerNotFoundError(f"Player {player_id} not found")
|
||||||
|
return result
|
||||||
|
```
|
||||||
|
|
||||||
|
## Discord Command Patterns
|
||||||
|
|
||||||
|
### Always use @logged_command decorator
|
||||||
|
Eliminates boilerplate logging. Class must have `self.logger` attribute:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class PlayerInfoCommands(commands.Cog):
|
||||||
|
def __init__(self, bot):
|
||||||
|
self.bot = bot
|
||||||
|
self.logger = get_contextual_logger(f'{__name__}.PlayerInfoCommands')
|
||||||
|
|
||||||
|
@discord.app_commands.command(name="player")
|
||||||
|
@logged_command("/player")
|
||||||
|
async def player_command(self, interaction, name: str):
|
||||||
|
# Business logic only - no try/catch boilerplate needed
|
||||||
|
player = await player_service.get_player_by_name(name)
|
||||||
|
await interaction.followup.send(embed=create_embed(player))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Autocomplete: Use standalone functions (not methods)
|
||||||
|
```python
|
||||||
|
async def player_name_autocomplete(
|
||||||
|
interaction: discord.Interaction,
|
||||||
|
current: str,
|
||||||
|
) -> List[discord.app_commands.Choice[str]]:
|
||||||
|
if len(current) < 2:
|
||||||
|
return []
|
||||||
|
try:
|
||||||
|
players = await player_service.search_players(current, limit=25)
|
||||||
|
return [discord.app_commands.Choice(name=p.name, value=p.name) for p in players]
|
||||||
|
except Exception:
|
||||||
|
return [] # Never break autocomplete
|
||||||
|
|
||||||
|
class MyCommands(commands.Cog):
|
||||||
|
@discord.app_commands.command()
|
||||||
|
@discord.app_commands.autocomplete(name=player_name_autocomplete)
|
||||||
|
async def my_command(self, interaction, name: str): ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Embed emoji rules
|
||||||
|
Template methods auto-add emojis. Never double up:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# CORRECT - template adds emoji
|
||||||
|
embed = EmbedTemplate.success(title="Operation Completed") # Results in: "Operation Completed"
|
||||||
|
|
||||||
|
# WRONG - double emoji
|
||||||
|
embed = EmbedTemplate.success(title="Operation Completed") # Results in: " Operation Completed"
|
||||||
|
|
||||||
|
# For custom emoji, use create_base_embed
|
||||||
|
embed = EmbedTemplate.create_base_embed(title="Custom Title", color=EmbedColors.SUCCESS)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Service Layer
|
||||||
|
|
||||||
|
### Never bypass services for API calls
|
||||||
|
```python
|
||||||
|
# CORRECT
|
||||||
|
player = await player_service.get_player(player_id)
|
||||||
|
|
||||||
|
# WRONG - never do this
|
||||||
|
client = await player_service.get_client()
|
||||||
|
await client.get(f'players/{player_id}')
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key service methods
|
||||||
|
- `TeamService.get_team(team_id)` - not `get_team_by_id()`
|
||||||
|
- `PlayerService.search_players(query, limit, all_seasons=True)` - cross-season search
|
||||||
|
|
||||||
|
## Models
|
||||||
|
|
||||||
|
### Use from_api_data() classmethod
|
||||||
|
```python
|
||||||
|
player = Player.from_api_data(api_response)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database entities require id field
|
||||||
|
```python
|
||||||
|
class Player(SBABaseModel):
|
||||||
|
id: int = Field(..., description="Player ID from database") # Required, not Optional
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### Use aioresponses for HTTP mocking
|
||||||
|
```python
|
||||||
|
from aioresponses import aioresponses
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_player():
|
||||||
|
with aioresponses() as m:
|
||||||
|
m.get("https://api.example.com/v3/players/1", payload={"id": 1, "name": "Test"})
|
||||||
|
result = await api_client.get("players", object_id=1)
|
||||||
|
assert result["name"] == "Test"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Provide complete model data
|
||||||
|
Pydantic validates all fields. Use helper functions for test data:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def create_player_data(player_id: int, name: str, **kwargs):
|
||||||
|
return {"id": player_id, "name": name, "wara": 2.5, "season": 13, "pos_1": "CF", **kwargs}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Critical Rules
|
||||||
|
|
||||||
|
1. **Git**: Never commit directly to `main`. Create feature branches.
|
||||||
|
2. **Services**: Always use service layer methods, never direct API client access.
|
||||||
|
3. **Embeds**: Don't add emojis to titles when using template methods (success/error/warning/info).
|
||||||
|
4. **Tests**: Include docstrings explaining "what" and "why" for each test.
|
||||||
|
5. **Commits**: Do not commit without user approval.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Check `CLAUDE.md` files in directories for detailed patterns:
|
||||||
|
- `commands/CLAUDE.md` - Command architecture
|
||||||
|
- `services/CLAUDE.md` - Service patterns
|
||||||
|
- `models/CLAUDE.md` - Model validation
|
||||||
|
- `tests/CLAUDE.md` - Testing strategies
|
||||||
Loading…
Reference in New Issue
Block a user