major-domo-v2/commands/CLAUDE.md
Cal Corum ca325142d8 CLAUDE: Add comprehensive CLAUDE.md documentation files for AI agent guidance
Adding 17 CLAUDE.md files across the project to provide detailed context
and implementation guidelines for AI development agents:

Root Documentation:
- CLAUDE.md - Main project guide with Git workflow requirements

Component Documentation:
- commands/CLAUDE.md - Command architecture and patterns
- models/CLAUDE.md - Pydantic models and validation
- services/CLAUDE.md - Service layer and API interactions
- tasks/CLAUDE.md - Background tasks and automation
- tests/CLAUDE.md - Testing strategies and patterns
- utils/CLAUDE.md - Utility functions and decorators
- views/CLAUDE.md - Discord UI components and embeds

Command Package Documentation:
- commands/help/CLAUDE.md - Help system implementation
- commands/injuries/CLAUDE.md - Injury management commands
- commands/league/CLAUDE.md - League-wide commands
- commands/players/CLAUDE.md - Player information commands
- commands/profile/CLAUDE.md - User profile commands
- commands/teams/CLAUDE.md - Team information commands
- commands/transactions/CLAUDE.md - Transaction management
- commands/utilities/CLAUDE.md - Utility commands
- commands/voice/CLAUDE.md - Voice channel management

Key Updates:
- Updated .gitignore to track CLAUDE.md files in version control
- Added Git Workflow section requiring branch-based development
- Documented all architectural patterns and best practices
- Included comprehensive command/service implementation guides

These files provide essential context for AI agents working on the codebase,
ensuring consistent patterns, proper error handling, and maintainable code.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 20:30:07 -05:00

26 KiB

Commands Package Documentation

Discord Bot v2.0 - Scalable Command Architecture

This document outlines the command architecture, patterns, and best practices established for the SBA Discord Bot v2.0.

📁 Architecture Overview

Package Structure

commands/
├── README.md                    # This documentation
├── __init__.py                  # Future: Global command utilities
└── players/                     # Player-related commands
    ├── __init__.py             # Package setup with resilient loading
    └── info.py                 # Player information commands

Current Implementation Status (October 2025)

Implemented Packages

commands/
├── README.md                    # This documentation
├── __init__.py
├── players/                     # ✅ COMPLETED - Player information
│   ├── README.md               # Player commands documentation
│   ├── __init__.py
│   └── info.py                 # /player command
├── teams/                       # ✅ COMPLETED - Team information
│   ├── README.md               # Team commands documentation
│   ├── __init__.py
│   ├── info.py                 # /team, /teams commands
│   └── roster.py               # /roster command
├── league/                      # ✅ COMPLETED - League-wide features
│   ├── README.md               # League commands documentation
│   ├── __init__.py
│   ├── info.py                 # /league command
│   ├── standings.py            # /standings command
│   ├── schedule.py             # /schedule command
│   └── submit_scorecard.py     # /submit-scorecard command
├── transactions/                # ✅ COMPLETED - Trade & roster moves
│   ├── README.md               # Transaction commands documentation
│   ├── __init__.py
│   ├── management.py           # /mymoves, /legal commands
│   ├── trade.py                # /trade command with builder UI
│   ├── trade_channels.py       # /trade-channel commands
│   ├── trade_channel_tracker.py # Channel tracking service
│   └── dropadd.py              # /dropadd command
├── admin/                       # ✅ COMPLETED - Admin tools
│   ├── __init__.py
│   ├── management.py           # /sync, /shutdown commands
│   └── users.py                # User management commands
├── custom_commands/             # ✅ COMPLETED - Custom command system
│   ├── __init__.py
│   └── main.py                 # /custom commands CRUD
├── help/                        # ✅ COMPLETED - Help system
│   ├── README.md               # Help commands documentation
│   ├── __init__.py
│   └── main.py                 # /help, /help-create, /help-edit, etc.
├── profile/                     # ✅ COMPLETED - User profiles
│   ├── README.md               # Profile commands documentation
│   ├── __init__.py
│   └── main.py                 # /profile commands
├── injuries/                    # ✅ COMPLETED - Injury management
│   ├── README.md               # Injury commands documentation
│   ├── __init__.py
│   └── management.py           # /injury commands
├── dice/                        # ✅ COMPLETED - Dice rolling
│   ├── __init__.py
│   └── rolls.py                # /roll, /dice commands
├── voice/                       # ✅ COMPLETED - Voice channel management
│   ├── README.md               # Voice commands documentation
│   ├── __init__.py
│   ├── channels.py             # /voice-channel commands
│   ├── tracker.py              # Channel tracking
│   └── cleanup_service.py      # Automated cleanup task
├── utilities/                   # ✅ COMPLETED - Utility commands
│   ├── README.md               # Utilities documentation
│   ├── __init__.py
│   └── charts.py               # /chart commands
├── soak/                        # ✅ COMPLETED - SOAK features
│   ├── __init__.py
│   └── main.py                 # SOAK-related commands
└── examples/                    # 📚 REFERENCE - Migration examples
    ├── __init__.py
    ├── migration_example.py    # Example migration patterns
    └── enhanced_player.py      # Enhanced command example

Package Documentation Quick Reference

Each package has its own detailed README.md with implementation specifics:

Package README Key Commands
players/ README.md /player - Player information and stats
teams/ README.md /team, /teams, /roster - Team info and rosters
league/ README.md /league, /standings, /schedule, /submit-scorecard
transactions/ README.md /trade, /mymoves, /legal, /trade-channel, /dropadd
help/ README.md /help, /help-create, /help-edit, /help-delete, /help-list
profile/ README.md /profile - User profile management
injuries/ README.md /injury - Injury management and tracking
voice/ README.md /voice-channel - Voice channel creation and cleanup
utilities/ README.md /chart - Chart and utility commands

Future Expansion Ideas

  • Draft System - /draft commands for draft management
  • Advanced Stats - /player-compare, /player-rankings, /leaderboard
  • Team Analytics - /team-stats, /team-leaders
  • League Leaders - /leaders, /awards
  • Historical Data - /history, /records

🏗️ Design Principles

1. Single Responsibility

  • Each file handles 2-4 closely related commands
  • Clear logical grouping by domain (players, teams, etc.)
  • Focused functionality reduces complexity

2. Resilient Loading

  • One failed cog doesn't break the entire package
  • Loop-based loading with comprehensive error handling
  • Clear logging for debugging and monitoring

3. Scalable Architecture

  • Easy to add new packages and cogs
  • Consistent patterns across all command groups
  • Future-proof structure for bot growth

4. Modern Discord.py Patterns

  • Application commands (slash commands) only
  • Proper error handling with user-friendly messages
  • Async/await throughout
  • Type hints and comprehensive documentation

🔧 Implementation Patterns

Command Package Structure

Individual Command File (e.g., players/info.py)

"""
Player Information Commands

Implements slash commands for displaying player information and statistics.
"""
import logging
from typing import Optional

import discord
from discord.ext import commands

from services.player_service import player_service
from exceptions import BotException

logger = logging.getLogger(f'{__name__}.PlayerInfoCommands')


class PlayerInfoCommands(commands.Cog):
    """Player information and statistics command handlers."""
    
    def __init__(self, bot: commands.Bot):
        self.bot = bot
    
    @discord.app_commands.command(
        name="player",
        description="Display player information and statistics"
    )
    @discord.app_commands.describe(
        name="Player name to search for",
        season="Season to show stats for (defaults to current season)"
    )
    async def player_info(
        self,
        interaction: discord.Interaction,
        name: str,
        season: Optional[int] = None
    ):
        """Display player card with statistics."""
        try:
            # Always defer for potentially slow API calls
            await interaction.response.defer()
            
            # Command implementation here
            # Use logger for error logging
            # Create Discord embeds for responses
            
        except Exception as e:
            logger.error(f"Player info command error: {e}", exc_info=True)
            error_msg = "❌ Error retrieving player information."
            
            if interaction.response.is_done():
                await interaction.followup.send(error_msg, ephemeral=True)
            else:
                await interaction.response.send_message(error_msg, ephemeral=True)


async def setup(bot: commands.Bot):
    """Load the player info commands cog."""
    await bot.add_cog(PlayerInfoCommands(bot))

Package init.py with Resilient Loading

"""
Player Commands Package

This package contains all player-related Discord commands organized into focused modules.
"""
import logging
from discord.ext import commands

from .info import PlayerInfoCommands
# Future imports:
# from .search import PlayerSearchCommands
# from .stats import PlayerStatsCommands

logger = logging.getLogger(__name__)


async def setup_players(bot: commands.Bot):
    """
    Setup all player command modules.
    
    Returns:
        tuple: (successful_count, failed_count, failed_modules)
    """
    # Define all player command cogs to load
    player_cogs = [
        ("PlayerInfoCommands", PlayerInfoCommands),
        # Future cogs:
        # ("PlayerSearchCommands", PlayerSearchCommands),
        # ("PlayerStatsCommands", PlayerStatsCommands), 
    ]
    
    successful = 0
    failed = 0
    failed_modules = []
    
    for cog_name, cog_class in player_cogs:
        try:
            await bot.add_cog(cog_class(bot))
            logger.info(f"✅ Loaded {cog_name}")
            successful += 1
        except Exception as e:
            logger.error(f"❌ Failed to load {cog_name}: {e}", exc_info=True)
            failed += 1
            failed_modules.append(cog_name)
    
    # Log summary
    if failed == 0:
        logger.info(f"🎉 All {successful} player command modules loaded successfully")
    else:
        logger.warning(f"⚠️  Player commands loaded with issues: {successful} successful, {failed} failed")
    
    return successful, failed, failed_modules


# Export the setup function for easy importing
__all__ = ['setup_players', 'PlayerInfoCommands']

🔄 Smart Command Syncing

Hash-Based Change Detection

The bot implements smart command syncing that only updates Discord when commands actually change:

Development Mode:

  • Automatically detects command changes using SHA-256 hashing
  • Only syncs when changes are detected
  • Saves hash to .last_command_hash for comparison
  • Prevents unnecessary Discord API calls

Production Mode:

  • No automatic syncing
  • Commands must be manually synced using /sync command
  • Prevents accidental command updates in production

How It Works

  1. Hash Generation: Creates hash of command names, descriptions, and parameters
  2. Comparison: Compares current hash with stored hash from .last_command_hash
  3. Conditional Sync: Only syncs if hashes differ or no previous hash exists
  4. Hash Storage: Saves new hash after successful sync

Benefits

  • API Efficiency: Avoids Discord rate limits
  • Development Speed: Fast restarts when no command changes
  • Production Safety: No accidental command updates
  • Consistency: Commands stay consistent across restarts

🚀 Bot Integration

Command Loading in bot.py

async def setup_hook(self):
    """Called when the bot is starting up."""
    # Load command packages
    await self._load_command_packages()

    # Smart command syncing: auto-sync in development if changes detected
    config = get_config()
    if config.is_development:
        if await self._should_sync_commands():
            self.logger.info("Development mode: changes detected, syncing commands...")
            await self._sync_commands()
            await self._save_command_hash()
        else:
            self.logger.info("Development mode: no command changes detected, skipping sync")
    else:
        self.logger.info("Production mode: commands loaded but not auto-synced")

async def _load_command_packages(self):
    """Load all command packages with resilient error handling."""
    from commands.players import setup_players
    from commands.teams import setup_teams
    from commands.league import setup_league
    from commands.custom_commands import setup_custom_commands
    from commands.admin import setup_admin
    from commands.transactions import setup_transactions
    from commands.dice import setup_dice
    from commands.voice import setup_voice
    from commands.utilities import setup_utilities
    from commands.help import setup_help_commands
    from commands.profile import setup_profile_commands
    from commands.soak import setup_soak
    from commands.injuries import setup_injuries

    # Define command packages to load (current implementation)
    command_packages = [
        ("players", setup_players),
        ("teams", setup_teams),
        ("league", setup_league),
        ("custom_commands", setup_custom_commands),
        ("admin", setup_admin),
        ("transactions", setup_transactions),
        ("dice", setup_dice),
        ("voice", setup_voice),
        ("utilities", setup_utilities),
        ("help", setup_help_commands),
        ("profile", setup_profile_commands),
        ("soak", setup_soak),
        ("injuries", setup_injuries),
    ]

    # Loop-based loading with error isolation
    total_successful = 0
    total_failed = 0

    for package_name, setup_func in command_packages:
        try:
            successful, failed, failed_modules = await setup_func(self)
            total_successful += successful
            total_failed += failed

            if failed == 0:
                self.logger.info(f"✅ {package_name} commands loaded successfully ({successful} cogs)")
            else:
                self.logger.warning(
                    f"⚠️  {package_name} commands loaded with issues: "
                    f"{successful} successful, {failed} failed"
                )
                if failed_modules:
                    self.logger.warning(f"Failed modules: {', '.join(failed_modules)}")
        except Exception as e:
            self.logger.error(f"❌ Failed to load {package_name} package: {e}", exc_info=True)
            total_failed += 1

    # Log final summary
    if total_failed == 0:
        self.logger.info(f"🎉 All command packages loaded successfully ({total_successful} total cogs)")
    else:
        self.logger.warning(
            f"⚠️  Command loading completed with issues: "
            f"{total_successful} successful, {total_failed} failed"
        )

📋 Development Guidelines

Adding New Command Packages

Before Starting: Review existing package README files for reference implementations:

  • Check Package Documentation Quick Reference for relevant examples
  • Study similar packages for patterns (e.g., review teams/ README when creating roster commands)
  • Follow the @logged_command decorator pattern shown in existing commands

1. Create Package Structure

mkdir commands/your_package
touch commands/your_package/__init__.py
touch commands/your_package/your_command.py
touch commands/your_package/README.md  # Document your package!

2. Implement Command Module

  • Follow the pattern from existing packages (e.g., players/info.py, teams/info.py)
  • Use @logged_command decorator - eliminates boilerplate error handling
  • Use contextual logger: self.logger = get_contextual_logger(f'{__name__}.ClassName')
  • Always defer responses: await interaction.response.defer()
  • Type hints and comprehensive docstrings
  • See commands/examples/ for migration patterns

3. Create Package Setup Function

  • Follow the pattern from existing __init__.py files (e.g., players/__init__.py, teams/__init__.py)
  • Use loop-based cog loading with error isolation
  • Return tuple: (successful, failed, failed_modules)
  • Comprehensive logging with emojis for quick scanning

4. Document Your Package

5. Register in Bot

  • Add import to _load_command_packages() in bot.py
  • Add to command_packages list
  • Test in development environment
  • Verify commands sync correctly

Adding Commands to Existing Packages

1. Create New Command Module

# commands/players/search.py
class PlayerSearchCommands(commands.Cog):
    # Implementation
    pass

async def setup(bot: commands.Bot):
    await bot.add_cog(PlayerSearchCommands(bot))

2. Update Package init.py

from .search import PlayerSearchCommands

# Add to player_cogs list
player_cogs = [
    ("PlayerInfoCommands", PlayerInfoCommands),
    ("PlayerSearchCommands", PlayerSearchCommands),  # New cog
]

3. Test Import Structure

# Verify imports work
from commands.players import setup_players
from commands.players.search import PlayerSearchCommands

🎯 Best Practices

Command Implementation

  1. Always defer responses for API calls: await interaction.response.defer()
  2. Use ephemeral responses for errors: ephemeral=True
  3. Comprehensive error handling with try/except blocks
  4. User-friendly error messages with emojis
  5. Proper logging with context and stack traces
  6. Type hints on all parameters and return values
  7. Descriptive docstrings for commands and methods

Package Organization

  1. 2-4 commands per file maximum
  2. Logical grouping by functionality/domain
  3. Consistent naming patterns across packages
  4. Module-level logging for clean, consistent logs
  5. Loop-based cog loading for error resilience
  6. Comprehensive return values from setup functions

Error Handling

  1. Package-level isolation - one failed cog doesn't break the package
  2. Clear error logging with stack traces for debugging
  3. User-friendly messages that don't expose internal errors
  4. Graceful degradation when possible
  5. Metric reporting for monitoring (success/failure counts)

📊 Monitoring & Metrics

Startup Logging

The command loading system provides comprehensive metrics for all packages:

INFO - Loading players commands...
INFO - ✅ Loaded PlayerInfoCommands
INFO - 🎉 All 1 player command modules loaded successfully
INFO - ✅ players commands loaded successfully (1 cogs)

INFO - Loading teams commands...
INFO - ✅ Loaded TeamInfoCommands
INFO - ✅ Loaded TeamRosterCommands
INFO - 🎉 All 2 team command modules loaded successfully
INFO - ✅ teams commands loaded successfully (2 cogs)

[... similar output for all 13 packages ...]

INFO - 🎉 All command packages loaded successfully (N total cogs)

Error Scenarios

ERROR - ❌ Failed to load PlayerInfoCommands: <error details>
WARNING - ⚠️  Player commands loaded with issues: 0 successful, 1 failed
WARNING - Failed modules: PlayerInfoCommands

Package-Specific Logs

Each package maintains its own logging with package-level context. Check individual package README files for specific logging patterns and monitoring guidance.

Command Sync Logging

INFO - Development mode: changes detected, syncing commands...
INFO - Synced 1 commands to guild 123456789

or

INFO - Development mode: no command changes detected, skipping sync

🔧 Troubleshooting

Common Issues

Import Errors

  • Check that __init__.py files exist in all packages
  • Verify cog class names match imports
  • Ensure service dependencies are available

Command Not Loading

  • Check logs for specific error messages
  • Verify cog is added to the package's cog list
  • Test individual module imports in Python REPL

Commands Not Syncing

  • Check if running in development mode (config.is_development)
  • Verify .last_command_hash file permissions
  • Use manual /sync command for troubleshooting
  • Check Discord API rate limits

Performance Issues

  • Monitor command loading times in logs
  • Check for unnecessary API calls during startup
  • Verify hash-based sync is working correctly

Debugging Tips

  1. Use the logs - comprehensive logging shows exactly what's happening
  2. Test imports individually - isolate package/module issues
  3. Check hash file - verify command change detection is working
  4. Monitor Discord API - watch for rate limiting or errors
  5. Use development mode - auto-sync helps debug command issues

📦 Command Groups Pattern

⚠️ CRITICAL: Use app_commands.Group, NOT commands.GroupCog

Discord.py provides two ways to create command groups (e.g., /injury roll, /injury clear):

  1. app_commands.Group RECOMMENDED - Use this pattern
  2. commands.GroupCog AVOID - Has interaction timing issues

Why commands.GroupCog Fails

commands.GroupCog has a critical bug that causes duplicate interaction processing, leading to:

  • 404 "Unknown interaction" errors when trying to defer/respond
  • Interaction already acknowledged errors in error handlers
  • Commands fail randomly even with proper error handling

Root Cause: GroupCog triggers the command handler twice for a single interaction, causing the first execution to consume the interaction token before the second execution can respond.

Correct Pattern: app_commands.Group

Use the same pattern as ChartCategoryGroup and ChartManageGroup:

from discord import app_commands
from discord.ext import commands
from utils.decorators import logged_command

class InjuryGroup(app_commands.Group):
    """Injury management command group."""

    def __init__(self):
        super().__init__(
            name="injury",
            description="Injury management commands"
        )
        self.logger = get_contextual_logger(f'{__name__}.InjuryGroup')

    @app_commands.command(name="roll", description="Roll for injury")
    @logged_command("/injury roll")
    async def injury_roll(self, interaction: discord.Interaction, player_name: str):
        """Roll for injury using player's injury rating."""
        await interaction.response.defer()

        # Command implementation
        # No try/catch needed - @logged_command handles it

async def setup(bot: commands.Bot):
    """Setup function for loading the injury commands."""
    bot.tree.add_command(InjuryGroup())

Key Differences

Feature app_commands.Group commands.GroupCog
Registration bot.tree.add_command(Group()) await bot.add_cog(Cog(bot))
Initialization __init__(self) no bot param __init__(self, bot) requires bot
Decorator Support @logged_command works perfectly Causes duplicate execution
Interaction Handling Single execution, reliable Duplicate execution, 404 errors
Recommended Use All command groups Never use

Migration from GroupCog to Group

If you have an existing commands.GroupCog, convert it:

  1. Change class inheritance:

    # Before
    class InjuryCog(commands.GroupCog, name="injury"):
        def __init__(self, bot):
            self.bot = bot
            super().__init__()
    
    # After
    class InjuryGroup(app_commands.Group):
        def __init__(self):
            super().__init__(name="injury", description="...")
    
  2. Update registration:

    # Before
    await bot.add_cog(InjuryCog(bot))
    
    # After
    bot.tree.add_command(InjuryGroup())
    
  3. Remove duplicate interaction checks:

    # Before (needed for GroupCog bug workaround)
    if not interaction.response.is_done():
        await interaction.response.defer()
    
    # After (clean, simple)
    await interaction.response.defer()
    

Working Examples

Good examples to reference:

  • commands/utilities/charts.py - ChartManageGroup and ChartCategoryGroup
  • commands/injuries/management.py - InjuryGroup

Both use app_commands.Group successfully with @logged_command decorators.

🚦 Future Enhancements

Planned Features

  • Permission Decorators: Role-based command restrictions per package
  • Dynamic Loading: Hot-reload commands without bot restart
  • Usage Metrics: Command usage tracking and analytics
  • Rate Limiting: Per-command rate limiting for resource management

Architecture Improvements

  • Shared Utilities: Common embed builders, decorators, helpers
  • Configuration: Per-package configuration and feature flags
  • Testing: Automated testing for command packages
  • Documentation: Auto-generated command documentation
  • Monitoring: Health checks and performance metrics per package

This architecture provides a solid foundation for scaling the Discord bot while maintaining code quality, reliability, and developer productivity.


📊 Current Status Summary

Total Packages Implemented: 13

  • players - Player information commands
  • teams - Team information and roster commands
  • league - League-wide commands (standings, schedule, etc.)
  • transactions - Trade and roster management
  • admin - Admin and system commands
  • custom_commands - Custom command system
  • help - Help documentation system
  • profile - User profile management
  • injuries - Injury tracking and management
  • dice - Dice rolling utilities
  • voice - Voice channel management
  • utilities - Chart and utility commands
  • soak - SOAK feature commands

Architecture Maturity: Production-ready with comprehensive error handling, logging, and monitoring


Last Updated: October 2025 - Documentation updated to reflect all implemented packages Next Review: When new major command packages are added