From 90c68b878487d59eb1c4bdc8958108e784ebcb03 Mon Sep 17 00:00:00 2001 From: Logan Honeycutt Date: Sun, 7 Dec 2025 00:57:21 -0500 Subject: [PATCH 01/11] feat(cursor): add CLI for validating cursor rules and commands - Introduced `CursorCLI` for validating rules and commands against Tux project standards. - Added validation logic for rule frontmatter, description, content, and command structure. - Integrated a new pre-commit hook to validate cursor rules and commands during commits. --- .pre-commit-config.yaml | 9 + scripts/__init__.py | 2 + scripts/cursor.py | 472 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 483 insertions(+) create mode 100644 scripts/cursor.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e8c40f74..27276328 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -79,6 +79,15 @@ repos: additional_dependencies: - '@commitlint/cli' - '@commitlint/config-conventional' + - repo: local + hooks: + - id: validate-cursor-rules + name: Validate Cursor Rules & Commands + entry: uv run cursor + language: system + args: [--rules-dir, .cursor/rules, --commands-dir, .cursor/commands] + files: ^\.cursor/(rules|commands)/.*\.(mdc|md)$ + pass_filenames: false exclude: ^(\.archive/|.*typings/|node_modules/|\.venv/|\.kiro/|src/tux/database/migrations/versions/).*$ ci: autofix_commit_msg: 'style: auto fixes from pre-commit hooks' diff --git a/scripts/__init__.py b/scripts/__init__.py index 181ede22..1dd1c23d 100644 --- a/scripts/__init__.py +++ b/scripts/__init__.py @@ -7,6 +7,7 @@ from scripts.base import BaseCLI from scripts.config import ConfigCLI +from scripts.cursor import CursorCLI from scripts.db import DatabaseCLI from scripts.dev import DevCLI from scripts.docker_cli import DockerCLI @@ -22,6 +23,7 @@ "CommandGroup", "CommandRegistry", "ConfigCLI", + "CursorCLI", "DatabaseCLI", "DevCLI", "DockerCLI", diff --git a/scripts/cursor.py b/scripts/cursor.py new file mode 100644 index 00000000..5cd4a186 --- /dev/null +++ b/scripts/cursor.py @@ -0,0 +1,472 @@ +#!/usr/bin/env python3 + +"""Cursor rules and commands validation CLI for Tux. + +This script provides commands for validating Cursor rules and commands +to ensure they follow the Tux project standards. +""" + +import re +from pathlib import Path +from typing import Annotated + +import typer +from rich.panel import Panel +from rich.table import Table +from typer import Option # type: ignore[attr-defined] + +from scripts.base import BaseCLI +from scripts.registry import Command + + +class CursorCLI(BaseCLI): + """Cursor rules and commands validation CLI.""" + + def __init__(self) -> None: + """Initialize the CursorCLI.""" + super().__init__( + name="cursor", + description="Cursor rules and commands validation", + ) + self._setup_command_registry() + self._setup_commands() + + def _setup_command_registry(self) -> None: + """Set up the command registry with all cursor commands.""" + all_commands = [ + Command( + "validate", + self.validate, + "Validate all Cursor rules and commands", + ), + ] + + for cmd in all_commands: + self._command_registry.register_command(cmd) + + def _setup_commands(self) -> None: + """Set up all cursor CLI commands using the command registry.""" + + # Add a no-op callback to force Typer into subcommand mode + # This prevents Typer from treating a single command with only Options as the main command + @self.app.callback(invoke_without_command=False) + def _main_callback() -> None: # pyright: ignore[reportUnusedFunction] + """Cursor rules and commands validation CLI.""" + + # Now register commands as subcommands + for command in self._command_registry.get_commands().values(): + self.add_command( + command.func, + name=command.name, + help_text=command.help_text, + ) + + def _check_rule_frontmatter( + self, + file_path: Path, + content: str, + ) -> tuple[list[str], str | None]: + """Check rule frontmatter format. + + Parameters + ---------- + file_path : Path + Path to the rule file. + content : str + File content. + + Returns + ------- + tuple[list[str], str | None] + List of errors and frontmatter content (or None if invalid). + """ + errors: list[str] = [] + + if not content.startswith("---"): + errors.append(f"{file_path}: Rule must start with frontmatter (---)") + + if "---\n" not in content[4:]: + errors.append(f"{file_path}: Rule must have closing frontmatter (---)") + + frontmatter_match = re.match(r"^---\n(.*?)\n---\n", content, re.DOTALL) + if not frontmatter_match: + errors.append(f"{file_path}: Invalid frontmatter format") + return errors, None + + frontmatter = frontmatter_match[1] + + # Check globs format (must be comma-separated, not array format) + if "globs:" in frontmatter: + globs_line_match = re.search(r"^globs:\s*(.+)$", frontmatter, re.MULTILINE) + if globs_line_match: + globs_value = globs_line_match[1].strip() + # Check for array format (incorrect) + if globs_value.startswith("[") and globs_value.endswith("]"): + errors.append( + f"{file_path}: globs must use comma-separated format, not array format. " + f"Use 'globs: pattern1, pattern2' instead of 'globs: [\"pattern1\", \"pattern2\"]'", + ) + # Check for quoted individual patterns (incorrect) + elif '"' in globs_value or "'" in globs_value: + errors.append( + f"{file_path}: globs patterns should not be quoted. " + f"Use 'globs: pattern1, pattern2' instead of 'globs: \"pattern1\", \"pattern2\"'", + ) + + return errors, frontmatter + + def _check_rule_description( + self, + file_path: Path, + frontmatter: str, + is_spec: bool, + is_reference: bool, + ) -> list[str]: + """Check rule description requirements. + + Parameters + ---------- + file_path : Path + Path to the rule file. + frontmatter : str + Frontmatter content. + is_spec : bool + Whether this is a specification file. + is_reference : bool + Whether this is a reference file. + + Returns + ------- + list[str] + List of errors. + """ + errors: list[str] = [] + + # Check description (required for intelligent rules, except specs) + if ( + "description:" not in frontmatter + and "alwaysApply: true" not in frontmatter + and not is_spec + ): + errors.append(f"{file_path}: Rule must include description field") + + # Check description length (skip for specs and references) + if not is_spec and not is_reference: # noqa: SIM102 + if desc_match := re.search(r"description:\s*(.+)", frontmatter): + desc = desc_match[1].strip().strip('"').strip("'") + if len(desc) < 60 or len(desc) > 120: + errors.append( + f"{file_path}: Description should be 60-120 chars (found {len(desc)})", + ) + + return errors + + def _check_rule_content( + self, + file_path: Path, + content: str, + frontmatter_end: int, + is_spec: bool, + is_reference: bool, + is_docs_rule: bool, + ) -> list[str]: + """Check rule content requirements. + + Parameters + ---------- + file_path : Path + Path to the rule file. + content : str + File content. + frontmatter_end : int + End position of frontmatter. + is_spec : bool + Whether this is a specification file. + is_reference : bool + Whether this is a reference file. + is_docs_rule : bool + Whether this is a documentation rule. + + Returns + ------- + list[str] + List of errors. + """ + errors: list[str] = [] + + if not is_reference: + body = content[frontmatter_end:] + if "# " not in body: + errors.append(f"{file_path}: Rule must have title (H1)") + + # Check for patterns section (skip for specs, references, and docs rules) + should_check_patterns = not (is_spec or is_reference or is_docs_rule) + if should_check_patterns: + if "✅" not in body and "❌" not in body: + errors.append( + f"{file_path}: Rule should include patterns with ✅ GOOD / ❌ BAD examples", + ) + + # Check for code examples + if "```" not in body: + errors.append(f"{file_path}: Rule should include code examples") + + return errors + + def _validate_rule(self, file_path: Path) -> list[str]: + """Validate a rule file. + + Parameters + ---------- + file_path : Path + Path to the rule file to validate. + + Returns + ------- + list[str] + List of error messages, empty if valid. + """ + errors: list[str] = [] + content = file_path.read_text(encoding="utf-8") + + # Special cases: reference files, specifications, and documentation rules + is_reference = file_path.name == "rules.mdc" + is_spec = "meta/" in str(file_path) + is_docs_rule = "docs/" in str(file_path) + is_large_reference = "ui/cv2.mdc" in str(file_path) # Large reference file + + # Check extension + if file_path.suffix != ".mdc": + errors.append(f"{file_path}: Rule must use .mdc extension") + + # Check frontmatter + frontmatter_errors, frontmatter = self._check_rule_frontmatter( + file_path, + content, + ) + errors.extend(frontmatter_errors) + if frontmatter is None: + return errors # Can't continue without valid frontmatter + + # Check description + errors.extend( + self._check_rule_description(file_path, frontmatter, is_spec, is_reference), + ) + + # Check size (max 500 lines, but allow exceptions for specs and large references) + line_count = len(content.split("\n")) + if line_count > 500 and not is_spec and not is_large_reference: + errors.append( + f"{file_path}: Rule exceeds 500 lines (found {line_count} lines)", + ) + + # Check content + if frontmatter_match := re.match(r"^---\n(.*?)\n---\n", content, re.DOTALL): + errors.extend( + self._check_rule_content( + file_path, + content, + frontmatter_match.end(), + is_spec, + is_reference, + is_docs_rule, + ), + ) + + return errors + + def _validate_command(self, file_path: Path) -> list[str]: + """Validate a command file. + + Parameters + ---------- + file_path : Path + Path to the command file to validate. + + Returns + ------- + list[str] + List of error messages, empty if valid. + """ + errors: list[str] = [] + content = file_path.read_text(encoding="utf-8") + + # Check extension + if file_path.suffix != ".md": + errors.append(f"{file_path}: Command must use .md extension") + + # Check for frontmatter (commands should NOT have frontmatter) + if content.startswith("---"): + errors.append( + f"{file_path}: Command must NOT have frontmatter (use plain Markdown)", + ) + + # Check for required sections + if "# " not in content: + errors.append(f"{file_path}: Command must have title (H1)") + + if "## Overview" not in content: + errors.append(f"{file_path}: Command must have Overview section") + + if "## Steps" not in content: + errors.append(f"{file_path}: Command must have Steps section") + + # Check for checklist (flexible naming) + if not re.search(r"## .*[Cc]hecklist", content): + errors.append(f"{file_path}: Command must have Checklist section") + + # Check for numbered steps + if not re.search(r"^\d+\.\s+\*\*", content, re.MULTILINE): + errors.append(f"{file_path}: Steps should be numbered (1., 2., etc.)") + + # Check checklist format + if not re.search(r"- \[ \]", content): + errors.append(f"{file_path}: Checklist should use - [ ] format") + + return errors + + def validate( + self, + rules_dir: Annotated[ + Path, + Option( + "--rules-dir", + help="Directory containing rule files", + ), + ] = Path(".cursor/rules"), + commands_dir: Annotated[ + Path, + Option( + "--commands-dir", + help="Directory containing command files", + ), + ] = Path(".cursor/commands"), + ) -> None: + """Validate all Cursor rules and commands. + + This command validates that all rules and commands follow the Tux project + standards for structure, content, and metadata. + + Parameters + ---------- + rules_dir : Path + Directory containing rule files (default: .cursor/rules). + commands_dir : Path + Directory containing command files (default: .cursor/commands). + + Raises + ------ + Exit + If validation fails. + """ + self.console.print( + Panel.fit("Cursor Rules & Commands Validator", style="bold blue"), + ) + + if not rules_dir.exists(): + self.console.print( + f"Error: {rules_dir} does not exist", + style="red", + ) + raise typer.Exit(code=1) + + if not commands_dir.exists(): + self.console.print( + f"Error: {commands_dir} does not exist", + style="red", + ) + raise typer.Exit(code=1) + + all_errors: list[str] = [] + + # Validate rules + rule_files = sorted(rules_dir.rglob("*.mdc")) + self.console.print(f"\n[bold]Validating {len(rule_files)} rule files...[/bold]") + for rule_file in rule_files: + errors = self._validate_rule(rule_file) + all_errors.extend(errors) + if errors: + for error in errors: + self.console.print(f" [red]✗[/red] {error}") + + # Validate commands + cmd_files = sorted(commands_dir.rglob("*.md")) + self.console.print( + f"\n[bold]Validating {len(cmd_files)} command files...[/bold]", + ) + for cmd_file in cmd_files: + errors = self._validate_command(cmd_file) + all_errors.extend(errors) + if errors: + for error in errors: + self.console.print(f" [red]✗[/red] {error}") + + # Report results + self._print_validation_summary( + rule_files, + cmd_files, + all_errors, + ) + + if all_errors: + raise typer.Exit(code=1) + + def _print_validation_summary( + self, + rule_files: list[Path], + cmd_files: list[Path], + all_errors: list[str], + ) -> None: + """Print validation summary table. + + Parameters + ---------- + rule_files : list[Path] + List of rule files validated. + cmd_files : list[Path] + List of command files validated. + all_errors : list[str] + List of validation errors. + """ + has_errors = len(all_errors) > 0 + header_style = "bold red" if has_errors else "bold green" + count_style = "red" if has_errors else "green" + + table = Table( + title="Validation Summary", + show_header=True, + header_style=header_style, + ) + table.add_column("Type", style="cyan", no_wrap=True) + table.add_column("Count", style=count_style, justify="right") + table.add_row("Rules", str(len(rule_files))) + table.add_row("Commands", str(len(cmd_files))) + + if has_errors: + table.add_row("Errors", str(len(all_errors)), style="bold red") + self.console.print("\n") + self.console.print(table) + self.console.print( + f"\n[bold red]Validation failed with {len(all_errors)} error(s)[/bold red]", + ) + else: + table.add_row("Status", "✓ All valid", style="bold green") + self.console.print("\n") + self.console.print(table) + self.console.print( + "\n[bold green]✓ All rules and commands are valid![/bold green]", + ) + + +# Create the CLI app instance +app = CursorCLI().app + + +def main() -> None: + """Entry point for the cursor CLI script.""" + cli = CursorCLI() + cli.run() + + +if __name__ == "__main__": + main() From 4c1c7d686bed177773b8de1686631283a7c66bdd Mon Sep 17 00:00:00 2001 From: Logan Honeycutt Date: Sun, 7 Dec 2025 00:57:50 -0500 Subject: [PATCH 02/11] feat(docs): add comprehensive documentation for code quality commands - Introduced new markdown files for linting, refactoring, reviewing diffs, and database operations. - Each file outlines steps, checklists, and error handling for maintaining code quality in the Tux project. - Enhanced documentation for Docker commands and testing procedures to streamline development processes. --- .cursor/commands/code-quality/lint.md | 59 ++++++++++++++++++ .cursor/commands/code-quality/refactor.md | 58 ++++++++++++++++++ .../code-quality/review-existing-diffs.md | 57 +++++++++++++++++ .cursor/commands/database/health.md | 47 ++++++++++++++ .cursor/commands/database/migration.md | 47 ++++++++++++++ .cursor/commands/database/reset.md | 49 +++++++++++++++ .cursor/commands/debugging/debug.md | 41 +++++++++++++ .cursor/commands/development/docker-down.md | 37 +++++++++++ .cursor/commands/development/docker-up.md | 40 ++++++++++++ .cursor/commands/development/setup-project.md | 61 +++++++++++++++++++ .cursor/commands/discord/create-module.md | 51 ++++++++++++++++ .cursor/commands/discord/test-command.md | 49 +++++++++++++++ .cursor/commands/documentation/docs-serve.md | 53 ++++++++++++++++ .../commands/documentation/generate-docs.md | 48 +++++++++++++++ .cursor/commands/documentation/update-docs.md | 49 +++++++++++++++ .../error-handling/add-error-handling.md | 47 ++++++++++++++ .cursor/commands/security/security-review.md | 56 +++++++++++++++++ .cursor/commands/testing/integration-tests.md | 48 +++++++++++++++ .../commands/testing/run-all-tests-and-fix.md | 47 ++++++++++++++ .cursor/commands/testing/test-coverage.md | 47 ++++++++++++++ .cursor/commands/testing/write-unit-tests.md | 58 ++++++++++++++++++ 21 files changed, 1049 insertions(+) create mode 100644 .cursor/commands/code-quality/lint.md create mode 100644 .cursor/commands/code-quality/refactor.md create mode 100644 .cursor/commands/code-quality/review-existing-diffs.md create mode 100644 .cursor/commands/database/health.md create mode 100644 .cursor/commands/database/migration.md create mode 100644 .cursor/commands/database/reset.md create mode 100644 .cursor/commands/debugging/debug.md create mode 100644 .cursor/commands/development/docker-down.md create mode 100644 .cursor/commands/development/docker-up.md create mode 100644 .cursor/commands/development/setup-project.md create mode 100644 .cursor/commands/discord/create-module.md create mode 100644 .cursor/commands/discord/test-command.md create mode 100644 .cursor/commands/documentation/docs-serve.md create mode 100644 .cursor/commands/documentation/generate-docs.md create mode 100644 .cursor/commands/documentation/update-docs.md create mode 100644 .cursor/commands/error-handling/add-error-handling.md create mode 100644 .cursor/commands/security/security-review.md create mode 100644 .cursor/commands/testing/integration-tests.md create mode 100644 .cursor/commands/testing/run-all-tests-and-fix.md create mode 100644 .cursor/commands/testing/test-coverage.md create mode 100644 .cursor/commands/testing/write-unit-tests.md diff --git a/.cursor/commands/code-quality/lint.md b/.cursor/commands/code-quality/lint.md new file mode 100644 index 00000000..89cbb935 --- /dev/null +++ b/.cursor/commands/code-quality/lint.md @@ -0,0 +1,59 @@ +# Lint and Fix Code + +## Overview + +Run Tux project linters (ruff, basedpyright) and automatically fix issues according to project coding standards. + +## Steps + +1. **Run Linters** + - Format code: `uv run ruff format .` + - Fix auto-fixable issues: `uv run ruff check --fix .` + - Check types: `uv run basedpyright` + - Run all checks: `uv run dev all` +2. **Identify Issues** + - Ruff linting violations (imports, style, complexity) + - Type errors from basedpyright + - Unused imports and variables + - Missing type hints (`Type | None` not `Optional[Type]`) + - NumPy docstring format violations +3. **Apply Fixes** + - Auto-fix with ruff where possible + - Add type hints with strict typing + - Use `Type | None` convention + - Fix import organization (stdlib → third-party → local) + - Update docstrings to NumPy format +4. **Verify Standards** + - 88 character line length + - snake_case for functions/variables + - PascalCase for classes + - UPPER_CASE for constants + - Absolute imports preferred + +## Error Handling + +If linting fails: + +- Review error messages carefully +- Some issues may require manual fixes +- Type errors may need code changes +- Import organization can be auto-fixed + +## Lint Checklist + +- [ ] Ran `uv run ruff format .` +- [ ] Ran `uv run ruff check --fix .` +- [ ] Ran `uv run basedpyright` +- [ ] Fixed all type errors +- [ ] Added missing type hints +- [ ] Used `Type | None` convention +- [ ] Organized imports correctly +- [ ] Updated docstrings to NumPy format +- [ ] Verified naming conventions +- [ ] Ran `uv run dev all` successfully + +## See Also + +- Related command: `/refactor` +- Related command: `/review-existing-diffs` +- Related rule: @core/tech-stack.mdc diff --git a/.cursor/commands/code-quality/refactor.md b/.cursor/commands/code-quality/refactor.md new file mode 100644 index 00000000..856dcb77 --- /dev/null +++ b/.cursor/commands/code-quality/refactor.md @@ -0,0 +1,58 @@ +# Refactor Code + +## Overview + +Refactor code to follow Tux project patterns, Discord.py best practices, and Python standards while maintaining functionality. + +## Steps + +1. **Code Quality Improvements** + - Extract reusable functions following DRY principle + - Eliminate code duplication across cogs + - Improve naming (snake_case, PascalCase, UPPER_CASE) + - Simplify nested async/await patterns +2. **Discord.py Patterns** + - Use hybrid commands (slash + traditional) + - Implement proper command error handlers + - Use embeds from `tux.ui.embeds` + - Follow cog structure from `src/modules/` +3. **Database Patterns** + - Use service layer for business logic + - Use controllers for database operations + - Never access database session directly + - Implement proper transaction handling +4. **Python Best Practices** + - Add strict type hints + - Use NumPy docstring format + - Follow async/await patterns + - Keep files under 1600 lines + +## Error Handling + +During refactoring: + +- Run tests frequently to catch regressions +- Use `uv run test quick` for fast feedback +- Check for breaking changes +- Verify all functionality still works + +## Refactor Checklist + +- [ ] Extracted reusable functions +- [ ] Eliminated code duplication +- [ ] Improved naming conventions +- [ ] Simplified complex nested logic +- [ ] Used proper Discord.py command patterns +- [ ] Implemented service layer architecture +- [ ] Added strict type hints +- [ ] Updated to NumPy docstrings +- [ ] Verified async/await patterns +- [ ] Kept files under 1600 lines +- [ ] Ran tests to verify functionality + +## See Also + +- Related command: `/lint` +- Related command: `/write-unit-tests` +- Related rule: @modules/cogs.mdc +- Related rule: @database/controllers.mdc diff --git a/.cursor/commands/code-quality/review-existing-diffs.md b/.cursor/commands/code-quality/review-existing-diffs.md new file mode 100644 index 00000000..e9e53f19 --- /dev/null +++ b/.cursor/commands/code-quality/review-existing-diffs.md @@ -0,0 +1,57 @@ +# Review Existing Diffs + +## Overview + +Perform quality pass on git diffs to ensure Tux project standards, test coverage, and documentation updates. + +## Steps + +1. **Scan Recent Changes** + - Check git status and pending commits + - Review modified files in `src/tux/` + - Note database model or migration changes + - Check for new commands or cogs +2. **Tux-Specific Checks** + - Database: Run `uv run db status` if models changed + - Tests: Verify test files updated in `tests/` + - Docs: Check if `docs/content/` needs updates + - Scripts: Validate CLI commands in `scripts/` +3. **Code Quality Signals** + - Run `uv run dev all` to check standards + - Verify type hints with basedpyright + - Check for TODOs or debug statements + - Ensure proper import organization +4. **Documentation Updates** + - Update docstrings if API changed + - Add CHANGELOG.md entry if needed + - Update docs if user-facing changes + - Verify conventional commit format + +## Error Handling + +If review finds issues: + +- Fix issues before committing +- Run `uv run dev all` after fixes +- Re-run tests to verify +- Update documentation if needed + +## Review Checklist + +- [ ] Ran `uv run dev all` successfully +- [ ] Database migrations created if models changed +- [ ] Tests added/updated for new functionality +- [ ] Documentation updated for user-facing changes +- [ ] No debug code or TODOs left in +- [ ] Type hints pass basedpyright checks +- [ ] Imports organized correctly +- [ ] Files under 1600 lines +- [ ] Conventional commit format used +- [ ] CHANGELOG.md updated if needed + +## See Also + +- Related command: `/lint` +- Related command: `/refactor` +- Related command: `/write-unit-tests` +- Related rule: @AGENTS.md diff --git a/.cursor/commands/database/health.md b/.cursor/commands/database/health.md new file mode 100644 index 00000000..d34e56d5 --- /dev/null +++ b/.cursor/commands/database/health.md @@ -0,0 +1,47 @@ +# Database Health Check + +## Overview + +Check database connection and health status for the Tux PostgreSQL database. + +## Steps + +1. **Check Connection** + - Run `uv run db health` + - Verify connection to PostgreSQL database + - Check database URL configuration + - Verify environment variables are set + +2. **Verify Services** + - Ensure Docker services are running: `uv run docker up` + - Check PostgreSQL container status + - Verify database is accessible + - Check connection pool status + +3. **Diagnose Issues** + - Review connection errors in logs + - Check `.env` file for correct database URL + - Verify `POSTGRES_*` environment variables + - Check Docker container logs if using Docker + +4. **Test Operations** + - Verify database service can create sessions + - Test basic query execution + - Check migration status: `uv run db status` + - Verify models can be accessed + +## Checklist + +- [ ] Database connection successful +- [ ] Environment variables configured correctly +- [ ] Docker services running (if using Docker) +- [ ] Database URL is valid +- [ ] Connection pool working +- [ ] Basic queries execute successfully +- [ ] Migration status accessible + +## See Also + +- Related rule: @database/services.mdc +- Related command: `/database-migration` +- Related command: `/database-reset` diff --git a/.cursor/commands/database/migration.md b/.cursor/commands/database/migration.md new file mode 100644 index 00000000..0a63c7ec --- /dev/null +++ b/.cursor/commands/database/migration.md @@ -0,0 +1,47 @@ +# Database Migration + +## Overview + +Create and apply Alembic database migrations for schema changes in the Tux project. + +## Steps + +1. **Create Migration** + - Run `uv run db migrate-dev "description of changes"` + - Review generated migration file in `src/tux/database/migrations/versions/` + - Verify upgrade and downgrade functions are correct + - Check for proper constraint naming + +2. **Review Migration** + - Ensure both `upgrade()` and `downgrade()` are implemented + - Verify all table/column operations are included + - Check index creation/dropping + - Validate enum type handling if applicable + +3. **Test Migration** + - Test upgrade: `uv run db push` (applies pending migrations) + - Test downgrade: Manually test rollback if needed + - Verify migration with `uv run db status` + - Run tests to ensure schema changes work + +4. **Apply Migration** + - Apply to development: `uv run db push` + - Check migration status: `uv run db status` + - Verify database health: `uv run db health` + +## Checklist + +- [ ] Migration file created with descriptive name +- [ ] Both upgrade and downgrade functions implemented +- [ ] All schema changes included (tables, columns, indexes, constraints) +- [ ] Enum types handled correctly if applicable +- [ ] Migration tested (upgrade and downgrade) +- [ ] Tests pass with new schema +- [ ] Migration applied to development database +- [ ] Database health check passes + +## See Also + +- Related rule: @database/migrations.mdc +- Related command: `/database-health` +- Related command: `/database-reset` diff --git a/.cursor/commands/database/reset.md b/.cursor/commands/database/reset.md new file mode 100644 index 00000000..7c2d13f9 --- /dev/null +++ b/.cursor/commands/database/reset.md @@ -0,0 +1,49 @@ +# Database Reset + +## Overview + +Safely reset the database by dropping all tables and reapplying migrations from scratch. Use with caution - this will delete all data. + +## Steps + +1. **Backup Data (if needed)** + - Export important data if this is production + - Note any custom data that needs to be preserved + - Document current database state + +2. **Reset Database** + - Run `uv run db reset` for safe reset + - This drops all tables and reapplies all migrations + - Verify reset completed successfully + - Check database is in clean state + +3. **Verify Reset** + - Check migration status: `uv run db status` + - Verify all tables recreated + - Test basic database operations + - Run health check: `uv run db health` + +4. **Re-seed Data (if needed)** + - Restore any required seed data + - Verify test data is correct + - Run tests to ensure everything works + +## Warning + +⚠️ **This will delete all data in the database!** Only use in development or when you're certain you want to lose all data. + +## Checklist + +- [ ] Data backed up (if needed) +- [ ] Database reset command executed +- [ ] All migrations reapplied successfully +- [ ] Database health check passes +- [ ] Basic operations work correctly +- [ ] Test data restored (if needed) +- [ ] Tests pass with reset database + +## See Also + +- Related rule: @database/migrations.mdc +- Related command: `/database-migration` +- Related command: `/database-health` diff --git a/.cursor/commands/debugging/debug.md b/.cursor/commands/debugging/debug.md new file mode 100644 index 00000000..c5a4345d --- /dev/null +++ b/.cursor/commands/debugging/debug.md @@ -0,0 +1,41 @@ +# Debug Issue + +## Overview + +Debug Discord bot issues systematically using Tux project tools and Discord.py debugging capabilities. + +## Steps + +1. **Problem Analysis** + - Check loguru output in `logs/` directory + - Review Sentry error traces + - Examine Discord API error codes + - Verify database state with `uv run db health` +2. **Discord Bot Debugging** + - Enable debug mode: `uv run tux start --debug` + - Check Discord gateway events and intents + - Verify command registration and sync + - Test interaction responses and timeouts +3. **Python Debugging Tools** + - Add breakpoints with `breakpoint()` + - Use loguru with `.debug()` level + - Run pytest with `-vv -s` for verbose output + - Check type errors with `uv run basedpyright` +4. **Database Debugging** + - Check migrations with `uv run db status` + - Verify queries with database logging + - Test with pytest database fixtures + - Use `uv run db reset` for clean state + +## Debug Checklist + +- [ ] Reviewed loguru logs in `logs/` directory +- [ ] Checked Sentry error dashboard +- [ ] Ran bot in debug mode +- [ ] Verified Discord gateway connection +- [ ] Checked command registration status +- [ ] Added debug logging at critical points +- [ ] Ran type checks with basedpyright +- [ ] Verified database migrations applied +- [ ] Ran relevant pytest tests with `-vv -s` +- [ ] Tested fix in isolated environment diff --git a/.cursor/commands/development/docker-down.md b/.cursor/commands/development/docker-down.md new file mode 100644 index 00000000..0315e033 --- /dev/null +++ b/.cursor/commands/development/docker-down.md @@ -0,0 +1,37 @@ +# Docker Down + +## Overview + +Stop Docker services and clean up containers. + +## Steps + +1. **Stop Docker Services** + - Run `uv run docker down` + - Wait for services to stop + - Verify containers are stopped + - Check cleanup completed + +2. **Verify Cleanup** + - Check containers are removed + - Verify volumes are handled correctly + - Check for any remaining processes + - Ensure clean shutdown + +## Notes + +- This stops all Docker services +- Database data may be lost if using default volumes +- Use with caution if you have important data + +## Checklist + +- [ ] Docker services stopped +- [ ] Containers removed +- [ ] Cleanup completed +- [ ] No remaining processes + +## See Also + +- Related command: `/docker-up` +- Related command: `/setup-project` diff --git a/.cursor/commands/development/docker-up.md b/.cursor/commands/development/docker-up.md new file mode 100644 index 00000000..36acb4e8 --- /dev/null +++ b/.cursor/commands/development/docker-up.md @@ -0,0 +1,40 @@ +# Docker Up + +## Overview + +Start Docker services (PostgreSQL, Adminer) for local development. + +## Steps + +1. **Start Docker Services** + - Run `uv run docker up` + - Wait for services to start + - Verify containers are running + - Check service health + +2. **Verify Services** + - Check PostgreSQL is accessible + - Verify Adminer is available + - Test database connection + - Check service logs if needed + +3. **Check Service Status** + - Review container status + - Verify ports are exposed correctly + - Check for any startup errors + - Ensure services are healthy + +## Checklist + +- [ ] Docker services started +- [ ] PostgreSQL container running +- [ ] Adminer container running +- [ ] Database connection works +- [ ] Services are healthy +- [ ] No startup errors + +## See Also + +- Related command: `/docker-down` +- Related command: `/setup-project` +- Related command: `/database-health` diff --git a/.cursor/commands/development/setup-project.md b/.cursor/commands/development/setup-project.md new file mode 100644 index 00000000..c00524f0 --- /dev/null +++ b/.cursor/commands/development/setup-project.md @@ -0,0 +1,61 @@ +# Setup Project + +## Overview + +Set up the Tux project for local development with all required dependencies and configuration. + +## Steps + +1. **Install Dependencies** + - Run `uv sync` to install all dependencies + - Verify dependencies installed correctly + - Check for any installation errors + - Review installed packages + +2. **Configure Environment** + - Copy `.env.example` to `.env` + - Configure required environment variables + - Set Discord bot token + - Configure database connection + +3. **Configure Application** + - Copy `config/config.toml.example` to `config/config.toml` + - Update configuration as needed + - Verify configuration is valid + - Test configuration loading + +4. **Start Services** + - Start Docker services: `uv run docker up` + - Wait for PostgreSQL to be ready + - Verify database connection: `uv run db health` + - Check all services are running + +5. **Initialize Database** + - Run `uv run db init` to initialize database + - Apply migrations: `uv run db push` + - Verify database schema + - Test database operations + +6. **Verify Setup** + - Run quality checks: `uv run dev all` + - Run tests: `uv run test quick` + - Start bot: `uv run tux start --debug` + - Verify bot connects to Discord + +## Checklist + +- [ ] Dependencies installed with `uv sync` +- [ ] Environment variables configured +- [ ] Configuration files created +- [ ] Docker services started +- [ ] Database initialized +- [ ] Migrations applied +- [ ] Quality checks pass +- [ ] Tests pass +- [ ] Bot starts successfully + +## See Also + +- Related command: `/docker-up` +- Related command: `/docker-down` +- Related rule: @core/tech-stack.mdc diff --git a/.cursor/commands/discord/create-module.md b/.cursor/commands/discord/create-module.md new file mode 100644 index 00000000..d284a39b --- /dev/null +++ b/.cursor/commands/discord/create-module.md @@ -0,0 +1,51 @@ +# Create Discord Module + +## Overview + +Create a new Discord bot module (cog) following Tux project patterns and structure. + +## Steps + +1. **Plan Module Structure** + - Determine module category (admin, config, features, fun, guild, info, levels, moderation, snippets, tools, utility) + - Choose appropriate directory in `src/tux/modules/` + - Plan command structure and functionality + - Identify required permissions + +2. **Create Module File** + - Create new file in appropriate module directory + - Inherit from `BaseCog` or specialized base (e.g., `ModerationCogBase`) + - Add `__init__` method with bot parameter + - Implement `setup` function for cog loading + +3. **Implement Commands** + - Use `@commands.hybrid_command` for hybrid commands + - Add `@commands.guild_only()` if guild required + - Add `@requires_command_permission()` for permissions + - Implement command handlers with proper error handling + - Use embeds from `tux.ui.embeds` for responses + +4. **Register Module** + - Add module to bot's cog loading system + - Test module loads correctly + - Verify commands are registered + +## Checklist + +- [ ] Module file created in appropriate directory +- [ ] Inherits from BaseCog or specialized base +- [ ] Commands use hybrid_command decorator +- [ ] Permission checks added with @requires_command_permission() +- [ ] Error handling implemented +- [ ] Uses embeds for responses +- [ ] Setup function implemented +- [ ] Module registered with bot +- [ ] Commands tested locally +- [ ] Commands synced to Discord + +## See Also + +- Related rule: @modules/cogs.mdc +- Related rule: @modules/commands.mdc +- Related command: `/discord-test-command` +- Related command: `/discord-sync-commands` diff --git a/.cursor/commands/discord/test-command.md b/.cursor/commands/discord/test-command.md new file mode 100644 index 00000000..07e2be28 --- /dev/null +++ b/.cursor/commands/discord/test-command.md @@ -0,0 +1,49 @@ +# Test Discord Command + +## Overview + +Test a Discord command locally to verify it works correctly before deploying. + +## Steps + +1. **Start Bot in Debug Mode** + - Run `uv run tux start --debug` + - Verify bot connects to Discord + - Check for any startup errors + - Verify command is loaded + +2. **Test Command Execution** + - Invoke command in Discord (slash or prefix) + - Test with valid inputs + - Test with invalid inputs + - Test permission checks + - Verify error handling works + +3. **Check Logs** + - Review loguru output for errors + - Check Sentry for any exceptions + - Verify command execution logs + - Check for rate limit warnings + +4. **Verify Responses** + - Check embeds render correctly + - Verify interaction responses work + - Test ephemeral responses if applicable + - Check button/select interactions + +## Checklist + +- [ ] Bot started in debug mode +- [ ] Command registered and visible +- [ ] Command executes with valid inputs +- [ ] Error handling works for invalid inputs +- [ ] Permission checks work correctly +- [ ] Responses render properly +- [ ] No errors in logs +- [ ] Interactions work correctly + +## See Also + +- Related rule: @modules/commands.mdc +- Related rule: @modules/interactions.mdc +- Related command: `/discord-create-module` diff --git a/.cursor/commands/documentation/docs-serve.md b/.cursor/commands/documentation/docs-serve.md new file mode 100644 index 00000000..250ddc70 --- /dev/null +++ b/.cursor/commands/documentation/docs-serve.md @@ -0,0 +1,53 @@ +# Serve Documentation + +## Overview + +Start local documentation server to preview documentation changes. + +## Steps + +1. **Start Documentation Server** + - Run `uv run docs serve` + - Server starts on default port (usually 8000) + - Open browser to view documentation + - Note the local URL + +2. **Preview Changes** + - Navigate to updated pages + - Check formatting and styling + - Test interactive features + - Verify code examples render + +3. **Test Navigation** + - Check all navigation links work + - Test search functionality + - Verify cross-references + - Check mobile responsiveness + +4. **Stop Server** + - Press Ctrl+C to stop server + - Or close terminal window + - Server stops automatically + +## Notes + +- Server auto-reloads on file changes +- Default port is usually 8000 +- Access at `http://localhost:8000` +- Changes are visible immediately + +## Checklist + +- [ ] Documentation server started +- [ ] Documentation accessible in browser +- [ ] Changes previewed +- [ ] Formatting verified +- [ ] Navigation tested +- [ ] Links work correctly +- [ ] Examples render properly + +## See Also + +- Related rule: @docs/zensical.mdc +- Related command: `/generate-docs` +- Related command: `/update-docs` diff --git a/.cursor/commands/documentation/generate-docs.md b/.cursor/commands/documentation/generate-docs.md new file mode 100644 index 00000000..b872822a --- /dev/null +++ b/.cursor/commands/documentation/generate-docs.md @@ -0,0 +1,48 @@ +# Generate Documentation + +## Overview + +Generate API documentation and update documentation for code changes. + +## Steps + +1. **Review Code Changes** + - Identify new functions, classes, or modules + - Note API changes or new features + - Check for breaking changes + - Review configuration changes + +2. **Update Documentation** + - Add new API documentation + - Update existing documentation + - Add code examples + - Update configuration examples + +3. **Build Documentation** + - Run `uv run docs build` to build site + - Check for build errors + - Verify all pages render correctly + - Test navigation and links + +4. **Review Generated Docs** + - Check API documentation is complete + - Verify examples work + - Test all links + - Ensure formatting is correct + +## Checklist + +- [ ] Code changes reviewed +- [ ] New documentation added +- [ ] Existing documentation updated +- [ ] Code examples included +- [ ] Documentation built successfully +- [ ] All pages render correctly +- [ ] Links work correctly +- [ ] Formatting is correct + +## See Also + +- Related rule: @docs/docs.mdc +- Related command: `/update-docs` +- Related command: `/docs-serve` diff --git a/.cursor/commands/documentation/update-docs.md b/.cursor/commands/documentation/update-docs.md new file mode 100644 index 00000000..dce5ee47 --- /dev/null +++ b/.cursor/commands/documentation/update-docs.md @@ -0,0 +1,49 @@ +# Update Documentation + +## Overview + +Update documentation to reflect code changes, new features, or configuration updates. + +## Steps + +1. **Identify Changes** + - Review code changes in current branch + - Note new features or commands + - Check for configuration changes + - Identify deprecated features + +2. **Update Relevant Sections** + - Update user guides for new features + - Update admin documentation for new commands + - Update developer docs for API changes + - Update configuration documentation + +3. **Follow Documentation Standards** + - Use Diátaxis framework (tutorial, how-to, reference, explanation) + - Follow writing style guidelines + - Use appropriate Zensical syntax + - Include practical examples + +4. **Test Documentation** + - Build documentation: `uv run docs build` + - Serve locally: `uv run docs serve` + - Review rendered pages + - Test all links and examples + +## Checklist + +- [ ] Code changes identified +- [ ] Relevant documentation sections updated +- [ ] Documentation standards followed +- [ ] Examples included +- [ ] Documentation built successfully +- [ ] Local preview reviewed +- [ ] Links tested +- [ ] Examples verified + +## See Also + +- Related rule: @docs/docs.mdc +- Related rule: @docs/style.mdc +- Related command: `/generate-docs` +- Related command: `/docs-serve` diff --git a/.cursor/commands/error-handling/add-error-handling.md b/.cursor/commands/error-handling/add-error-handling.md new file mode 100644 index 00000000..e6849739 --- /dev/null +++ b/.cursor/commands/error-handling/add-error-handling.md @@ -0,0 +1,47 @@ +# Add Error Handling + +## Overview + +Implement comprehensive error handling for Discord bot operations, database transactions, and async operations following Tux project patterns. + +## Steps + +1. **Discord Error Detection** + - Identify Discord API errors and rate limits + - Find unhandled command exceptions + - Detect permission and authorization failures + - Analyze interaction timeout scenarios +2. **Database Error Handling** + - Add transaction rollback on failures + - Handle SQLModel integrity constraints + - Implement connection retry logic + - Catch asyncpg-specific exceptions +3. **Bot-Specific Patterns** + - Use custom exceptions from `tux.shared.exceptions` + - Implement proper error embeds for user feedback + - Add Sentry integration for error tracking + - Use loguru for contextual error logging +4. **Async Error Handling** + - Handle asyncio cancellation properly + - Implement timeouts for long operations + - Use try/except in async contexts + - Add proper cleanup in finally blocks + +## Error Handling Checklist + +- [ ] Wrapped Discord API calls with appropriate error handlers +- [ ] Added database transaction rollback mechanisms +- [ ] Used custom exceptions from `tux.shared.exceptions` +- [ ] Implemented user-friendly error embeds +- [ ] Added Sentry error tracking where appropriate +- [ ] Used loguru with proper context logging +- [ ] Handled async cancellation and timeouts +- [ ] Added proper cleanup in finally blocks +- [ ] Tested error scenarios with pytest markers + +## See Also + +- Related rule: @error-handling/patterns.mdc +- Related rule: @error-handling/logging.mdc +- Related rule: @error-handling/sentry.mdc +- Related rule: @error-handling/user-feedback.mdc diff --git a/.cursor/commands/security/security-review.md b/.cursor/commands/security/security-review.md new file mode 100644 index 00000000..326a87f9 --- /dev/null +++ b/.cursor/commands/security/security-review.md @@ -0,0 +1,56 @@ +# Security Audit + +## Overview + +Comprehensive security review for Discord bot operations, database access, and environment configuration. + +## Steps + +1. **Dependency Audit** + - Run `uv run dev all` to check for vulnerabilities + - Review `uv.lock` for outdated packages + - Check Renovate PRs for security updates + - Audit third-party Discord libraries +2. **Bot Security Review** + - Verify Discord token handling in `.env` + - Check permission checks in commands + - Review role-based access control + - Audit user input validation and sanitization +3. **Database Security** + - Verify no raw SQL injection vectors + - Check database credentials in `.env` + - Audit SQLModel query patterns + - Review database migration safety +4. **Configuration Security** + - Ensure no secrets in `config.toml` + - Verify `.env` not committed + - Check Sentry DSN exposure + - Review Docker secrets management + +## Error Handling + +If security issues found: + +- Fix immediately for critical issues +- Document findings +- Update security practices +- Review and update related code + +## Security Checklist + +- [ ] Dependencies scanned with `uv` security checks +- [ ] No Discord tokens in code or config files +- [ ] Permission checks on all privileged commands +- [ ] Input validation on all user-provided data +- [ ] Database queries use SQLModel ORM (no raw SQL) +- [ ] Database credentials only in `.env` +- [ ] `.env` in `.gitignore` and not committed +- [ ] Sentry integration doesn't expose sensitive data +- [ ] Docker secrets properly mounted + +## See Also + +- Related rule: @security/patterns.mdc +- Related rule: @security/secrets.mdc +- Related rule: @security/validation.mdc +- Related rule: @security/dependencies.mdc diff --git a/.cursor/commands/testing/integration-tests.md b/.cursor/commands/testing/integration-tests.md new file mode 100644 index 00000000..d3b7be65 --- /dev/null +++ b/.cursor/commands/testing/integration-tests.md @@ -0,0 +1,48 @@ +# Integration Tests + +## Overview + +Run integration tests to verify components work together correctly. + +## Steps + +1. **Run Integration Tests** + - Execute `uv run pytest -m integration` + - Review integration test results + - Check for database integration issues + - Verify service layer integration + +2. **Check Database Integration** + - Verify database fixtures work + - Test database operations + - Check migration integration + - Verify controller integration + +3. **Verify Service Integration** + - Test service layer interactions + - Check Discord API integration + - Verify external service integration + - Test error handling integration + +4. **Fix Integration Issues** + - Fix service integration problems + - Update integration tests if needed + - Ensure all integration tests pass + - Verify end-to-end workflows + +## Checklist + +- [ ] Integration tests executed +- [ ] Database integration verified +- [ ] Service integration verified +- [ ] External service integration checked +- [ ] All integration tests pass +- [ ] End-to-end workflows work +- [ ] No integration errors + +## See Also + +- Related rule: @testing/pytest.mdc +- Related rule: @testing/fixtures.mdc +- Related command: `/run-all-tests-and-fix` +- Related command: `/test-coverage` diff --git a/.cursor/commands/testing/run-all-tests-and-fix.md b/.cursor/commands/testing/run-all-tests-and-fix.md new file mode 100644 index 00000000..11b9d5e8 --- /dev/null +++ b/.cursor/commands/testing/run-all-tests-and-fix.md @@ -0,0 +1,47 @@ +# Run All Tests and Fix + +## Overview + +Run the full test suite and fix any failures according to Tux project testing standards. + +## Steps + +1. **Run Full Test Suite** + - Execute `uv run test all` for full coverage + - Review test results and failures + - Identify failing tests and error messages + - Check coverage report + +2. **Analyze Failures** + - Read error messages and tracebacks + - Identify root cause of failures + - Check if failures are due to code changes + - Determine if tests need updates or code needs fixes + +3. **Fix Issues** + - Fix code issues causing test failures + - Update tests if requirements changed + - Ensure all tests pass + - Verify coverage meets requirements + +4. **Re-run Tests** + - Run `uv run test all` again + - Verify all tests pass + - Check coverage improved + - Review test output for warnings + +## Checklist + +- [ ] Full test suite executed +- [ ] All test failures identified +- [ ] Root causes determined +- [ ] Code or tests fixed +- [ ] All tests pass +- [ ] Coverage meets requirements +- [ ] No test warnings + +## See Also + +- Related rule: @testing/pytest.mdc +- Related command: `/test-coverage` +- Related command: `/integration-tests` diff --git a/.cursor/commands/testing/test-coverage.md b/.cursor/commands/testing/test-coverage.md new file mode 100644 index 00000000..2a00fdc6 --- /dev/null +++ b/.cursor/commands/testing/test-coverage.md @@ -0,0 +1,47 @@ +# Test Coverage + +## Overview + +Generate and review test coverage reports to ensure adequate test coverage. + +## Steps + +1. **Generate Coverage Report** + - Run `uv run test html` for HTML report + - Or `uv run test all` for terminal report + - Review coverage percentages + - Identify uncovered code + +2. **Analyze Coverage** + - Check overall coverage percentage + - Review coverage by module + - Identify critical paths with low coverage + - Note missing edge case tests + +3. **Improve Coverage** + - Add tests for uncovered code + - Test edge cases and error paths + - Add integration tests where needed + - Focus on critical business logic + +4. **Review Coverage Goals** + - Aim for 80%+ overall coverage + - Ensure 100% coverage on critical paths + - Maintain high coverage on new code + - Gradually improve legacy code coverage + +## Checklist + +- [ ] Coverage report generated +- [ ] Coverage percentages reviewed +- [ ] Uncovered code identified +- [ ] Tests added for uncovered code +- [ ] Coverage goals met (80%+ overall) +- [ ] Critical paths have 100% coverage +- [ ] Coverage report reviewed + +## See Also + +- Related rule: @testing/coverage.mdc +- Related command: `/run-all-tests-and-fix` +- Related command: `/integration-tests` diff --git a/.cursor/commands/testing/write-unit-tests.md b/.cursor/commands/testing/write-unit-tests.md new file mode 100644 index 00000000..4d5d962e --- /dev/null +++ b/.cursor/commands/testing/write-unit-tests.md @@ -0,0 +1,58 @@ +# Write Unit Tests + +## Overview + +Create comprehensive unit tests for the current code and generate the test file with proper imports and setup according to the project's testing conventions. + +## Steps + +1. **Test Coverage** + - Test all public methods and functions + - Cover edge cases and error conditions + - Test both positive and negative scenarios + - Aim for high code coverage +2. **Test Structure** + - Use the project's testing framework conventions + - Write clear, descriptive test names + - Follow the Arrange-Act-Assert pattern + - Group related tests logically +3. **Test Cases to Include** + - Happy path scenarios + - Edge cases and boundary conditions + - Error handling and exception cases + - Mock external dependencies appropriately +4. **Test Quality** + - Make tests independent and isolated + - Ensure tests are deterministic and repeatable + - Keep tests simple and focused on one thing + - Add helpful assertion messages + +## Error Handling + +If tests fail: + +- Review error messages and tracebacks +- Check fixture setup and teardown +- Verify database state if using database fixtures +- Ensure async tests use @pytest.mark.asyncio + +## Write Unit Tests Checklist + +- [ ] Tested all public methods and functions +- [ ] Covered edge cases and error conditions +- [ ] Tested both positive and negative scenarios +- [ ] Used the project's testing framework conventions +- [ ] Written clear, descriptive test names +- [ ] Followed the Arrange-Act-Assert pattern +- [ ] Included happy path scenarios +- [ ] Included edge cases and boundary conditions +- [ ] Mocked external dependencies appropriately +- [ ] Made tests independent and isolated +- [ ] Ensured tests are deterministic and repeatable + +## See Also + +- Related command: `/run-all-tests-and-fix` +- Related command: `/test-coverage` +- Related rule: @testing/pytest.mdc +- Related rule: @testing/fixtures.mdc From 067a7f6bdfc45be59d1e20ad9447cbb863018802 Mon Sep 17 00:00:00 2001 From: Logan Honeycutt Date: Sun, 7 Dec 2025 00:58:54 -0500 Subject: [PATCH 03/11] fix(cursor): update pre-commit hook entry for cursor validation - Changed the entry command for the cursor validation hook to include 'validate' for improved clarity and functionality. - Removed unnecessary arguments from the hook configuration to streamline the validation process. --- .pre-commit-config.yaml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 27276328..c7d64bd9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -83,11 +83,8 @@ repos: hooks: - id: validate-cursor-rules name: Validate Cursor Rules & Commands - entry: uv run cursor + entry: uv run cursor validate language: system - args: [--rules-dir, .cursor/rules, --commands-dir, .cursor/commands] - files: ^\.cursor/(rules|commands)/.*\.(mdc|md)$ - pass_filenames: false exclude: ^(\.archive/|.*typings/|node_modules/|\.venv/|\.kiro/|src/tux/database/migrations/versions/).*$ ci: autofix_commit_msg: 'style: auto fixes from pre-commit hooks' From a88dd0c4c5aeb1738dbfff79e9afe34f976b4838 Mon Sep 17 00:00:00 2001 From: Logan Honeycutt Date: Sun, 7 Dec 2025 01:09:24 -0500 Subject: [PATCH 04/11] feat(rules): implement CLI for validating rules and commands - Added a new `RulesCLI` for validating rules and commands against Tux project standards. - Updated pre-commit configuration to use the new validation command. - Enhanced the `pyproject.toml` to include the rules CLI entry point. - Refactored existing code to integrate the new validation logic and ensure proper command structure. --- .pre-commit-config.yaml | 9 +++++--- pyproject.toml | 1 + scripts/__init__.py | 4 ++-- scripts/{cursor.py => rules.py} | 37 ++++++++++++++++++--------------- 4 files changed, 29 insertions(+), 22 deletions(-) rename scripts/{cursor.py => rules.py} (94%) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c7d64bd9..96890de6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -81,10 +81,13 @@ repos: - '@commitlint/config-conventional' - repo: local hooks: - - id: validate-cursor-rules - name: Validate Cursor Rules & Commands - entry: uv run cursor validate + - id: validate-rules + name: Validate Rules & Commands + entry: uv run rules validate language: system + args: [--rules-dir, .cursor/rules, --commands-dir, .cursor/commands] + files: \.cursor/(rules|commands)/.*(\.mdc|\.md)$ + pass_filenames: false exclude: ^(\.archive/|.*typings/|node_modules/|\.venv/|\.kiro/|src/tux/database/migrations/versions/).*$ ci: autofix_commit_msg: 'style: auto fixes from pre-commit hooks' diff --git a/pyproject.toml b/pyproject.toml index 4b0dcc4c..e038c403 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,6 +65,7 @@ tests = "scripts.test:main" docker = "scripts.docker_cli:main" docs = "scripts.docs:main" config = "scripts.config:main" +rules = "scripts.rules:main" [dependency-groups] dev = [ diff --git a/scripts/__init__.py b/scripts/__init__.py index 1dd1c23d..9b37c92d 100644 --- a/scripts/__init__.py +++ b/scripts/__init__.py @@ -7,13 +7,13 @@ from scripts.base import BaseCLI from scripts.config import ConfigCLI -from scripts.cursor import CursorCLI from scripts.db import DatabaseCLI from scripts.dev import DevCLI from scripts.docker_cli import DockerCLI from scripts.docs import DocsCLI from scripts.registry import Command, CommandGroup, CommandRegistry from scripts.rich_utils import RichCLI +from scripts.rules import RulesCLI from scripts.test import TestCLI from scripts.tux import TuxCLI @@ -23,7 +23,7 @@ "CommandGroup", "CommandRegistry", "ConfigCLI", - "CursorCLI", + "RulesCLI", "DatabaseCLI", "DevCLI", "DockerCLI", diff --git a/scripts/cursor.py b/scripts/rules.py similarity index 94% rename from scripts/cursor.py rename to scripts/rules.py index 5cd4a186..3cdef4a5 100644 --- a/scripts/cursor.py +++ b/scripts/rules.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 -"""Cursor rules and commands validation CLI for Tux. +"""Rules and commands validation CLI for Tux. -This script provides commands for validating Cursor rules and commands +This script provides commands for validating rules and commands to ensure they follow the Tux project standards. """ @@ -19,25 +19,25 @@ from scripts.registry import Command -class CursorCLI(BaseCLI): - """Cursor rules and commands validation CLI.""" +class RulesCLI(BaseCLI): + """Rules and commands validation CLI.""" def __init__(self) -> None: - """Initialize the CursorCLI.""" + """Initialize the RulesCLI.""" super().__init__( - name="cursor", - description="Cursor rules and commands validation", + name="rules", + description="Rules and commands validation", ) self._setup_command_registry() self._setup_commands() def _setup_command_registry(self) -> None: - """Set up the command registry with all cursor commands.""" + """Set up the command registry with all rules commands.""" all_commands = [ Command( "validate", self.validate, - "Validate all Cursor rules and commands", + "Validate all rules and commands", ), ] @@ -45,13 +45,13 @@ def _setup_command_registry(self) -> None: self._command_registry.register_command(cmd) def _setup_commands(self) -> None: - """Set up all cursor CLI commands using the command registry.""" + """Set up all rules CLI commands using the command registry.""" # Add a no-op callback to force Typer into subcommand mode # This prevents Typer from treating a single command with only Options as the main command @self.app.callback(invoke_without_command=False) def _main_callback() -> None: # pyright: ignore[reportUnusedFunction] - """Cursor rules and commands validation CLI.""" + """Rules and commands validation CLI.""" # Now register commands as subcommands for command in self._command_registry.get_commands().values(): @@ -96,9 +96,12 @@ def _check_rule_frontmatter( frontmatter = frontmatter_match[1] # Check globs format (must be comma-separated, not array format) - if "globs:" in frontmatter: - globs_line_match = re.search(r"^globs:\s*(.+)$", frontmatter, re.MULTILINE) - if globs_line_match: + if "globs:" in frontmatter: # noqa: SIM102 + if globs_line_match := re.search( + r"^globs:\s*(.+)$", + frontmatter, + re.MULTILINE, + ): globs_value = globs_line_match[1].strip() # Check for array format (incorrect) if globs_value.startswith("[") and globs_value.endswith("]"): @@ -342,7 +345,7 @@ def validate( ), ] = Path(".cursor/commands"), ) -> None: - """Validate all Cursor rules and commands. + """Validate all rules and commands. This command validates that all rules and commands follow the Tux project standards for structure, content, and metadata. @@ -459,12 +462,12 @@ def _print_validation_summary( # Create the CLI app instance -app = CursorCLI().app +app = RulesCLI().app def main() -> None: """Entry point for the cursor CLI script.""" - cli = CursorCLI() + cli = RulesCLI() cli.run() From 9dc3366c91e4bb4ff6d9fae9e4cc32436f211a42 Mon Sep 17 00:00:00 2001 From: Logan Honeycutt Date: Sun, 7 Dec 2025 01:09:40 -0500 Subject: [PATCH 05/11] feat(rules): expand and organize Cursor rules and commands documentation - Added a comprehensive catalog of all Cursor rules and commands for the Tux project, enhancing clarity and accessibility. - Updated individual rule files with detailed descriptions, best practices, and anti-patterns for various components, including database, modules, and security. - Introduced new files for database migrations, queries, and service patterns, ensuring a structured approach to database operations. - Enhanced documentation for error handling, logging, and testing patterns to improve overall code quality and maintainability. - Removed outdated overview files and streamlined the organization of rules for better navigation and usability. --- .cursor/rules/core/tech-stack.mdc | 26 +- .cursor/rules/database/controllers.mdc | 519 +++++----------- .cursor/rules/database/migrations.mdc | 249 ++++++++ .cursor/rules/database/models.mdc | 569 +++++------------- .cursor/rules/database/overview.mdc | 180 ------ .cursor/rules/database/queries.mdc | 208 +++++++ .cursor/rules/database/service.mdc | 241 -------- .cursor/rules/database/services.mdc | 197 ++++++ .cursor/rules/docs/docs.mdc | 11 + .cursor/rules/error-handling/logging.mdc | 155 +++++ .cursor/rules/error-handling/patterns.mdc | 163 +++++ .cursor/rules/error-handling/sentry.mdc | 132 ++++ .../rules/error-handling/user-feedback.mdc | 146 +++++ .cursor/rules/meta/cursor-commands.mdc | 203 +++++++ .cursor/rules/meta/cursor-rules.mdc | 160 +++++ .cursor/rules/modules/cogs.mdc | 224 +++++++ .cursor/rules/modules/commands.mdc | 319 ++++++++++ .cursor/rules/modules/events.mdc | 265 ++++++++ .cursor/rules/modules/interactions.mdc | 298 +++++++++ .cursor/rules/modules/permissions.mdc | 156 +++++ .cursor/rules/rules.mdc | 126 +++- .cursor/rules/security/dependencies.mdc | 101 ++++ .cursor/rules/security/patterns.mdc | 178 ++++++ .cursor/rules/security/secrets.mdc | 159 +++++ .cursor/rules/security/validation.mdc | 191 ++++++ .cursor/rules/testing/async.mdc | 159 +++++ .cursor/rules/testing/coverage.mdc | 134 +++++ .cursor/rules/testing/fixtures.mdc | 206 +++++++ .cursor/rules/testing/markers.mdc | 194 ++++++ .cursor/rules/testing/pytest.mdc | 191 ++++++ .cursor/rules/ui/cv2.mdc | 25 +- 31 files changed, 4872 insertions(+), 1213 deletions(-) create mode 100644 .cursor/rules/database/migrations.mdc delete mode 100644 .cursor/rules/database/overview.mdc create mode 100644 .cursor/rules/database/queries.mdc delete mode 100644 .cursor/rules/database/service.mdc create mode 100644 .cursor/rules/database/services.mdc create mode 100644 .cursor/rules/error-handling/logging.mdc create mode 100644 .cursor/rules/error-handling/patterns.mdc create mode 100644 .cursor/rules/error-handling/sentry.mdc create mode 100644 .cursor/rules/error-handling/user-feedback.mdc create mode 100644 .cursor/rules/meta/cursor-commands.mdc create mode 100644 .cursor/rules/meta/cursor-rules.mdc create mode 100644 .cursor/rules/modules/cogs.mdc create mode 100644 .cursor/rules/modules/commands.mdc create mode 100644 .cursor/rules/modules/events.mdc create mode 100644 .cursor/rules/modules/interactions.mdc create mode 100644 .cursor/rules/modules/permissions.mdc create mode 100644 .cursor/rules/security/dependencies.mdc create mode 100644 .cursor/rules/security/patterns.mdc create mode 100644 .cursor/rules/security/secrets.mdc create mode 100644 .cursor/rules/security/validation.mdc create mode 100644 .cursor/rules/testing/async.mdc create mode 100644 .cursor/rules/testing/coverage.mdc create mode 100644 .cursor/rules/testing/fixtures.mdc create mode 100644 .cursor/rules/testing/markers.mdc create mode 100644 .cursor/rules/testing/pytest.mdc diff --git a/.cursor/rules/core/tech-stack.mdc b/.cursor/rules/core/tech-stack.mdc index 889dcd30..1b3593ca 100644 --- a/.cursor/rules/core/tech-stack.mdc +++ b/.cursor/rules/core/tech-stack.mdc @@ -1,7 +1,7 @@ --- -alwaysApply: true +description: Tech stack, dependencies, project structure, and development tools for Tux Discord bot +alwaysApply: false --- - # Tech Stack & Dependencies ## Core Runtime @@ -119,6 +119,8 @@ tux/ ## Development Workflow +✅ **GOOD:** Use project scripts + ```bash # Setup uv sync # Install all dependencies @@ -129,14 +131,16 @@ cp config/config.toml.example config/config.toml uv run dev all # Run all quality checks uv run tests quick # Fast test run uv run tux start --debug # Start bot in debug mode +``` -# Database -uv run db migrate-dev # Create & apply migrations -uv run db health # Check database connection +❌ **BAD:** Don't use Python directly or skip setup -# Documentation -uv run docs serve # Local preview -uv run docs build # Build static site +```bash +# ❌ BAD: Using Python directly (lacks venv deps) +python src/tux/main.py + +# ❌ BAD: Skipping dependency installation +# Missing: uv sync ``` ## Configuration System @@ -192,3 +196,9 @@ uv run docs build # Build static site 8. **Docker:** Use multi-stage builds, non-root users 9. **Monitoring:** Structured logging, error tracking 10. **Security:** Regular dependency updates, secret management + +## See Also + +- @database/models.mdc - Database model patterns +- @testing/pytest.mdc - Testing patterns +- @AGENTS.md - General coding standards diff --git a/.cursor/rules/database/controllers.mdc b/.cursor/rules/database/controllers.mdc index b356963f..a8fa00a3 100644 --- a/.cursor/rules/database/controllers.mdc +++ b/.cursor/rules/database/controllers.mdc @@ -1,467 +1,246 @@ --- -alwaysApply: true -description: Database controller patterns, composition, and usage guidelines +description: Database controller patterns, BaseController usage, CRUD operations, and query patterns for Tux +globs: src/tux/database/controllers/**/*.py --- -# Database Controller Rules +# Database Controllers -Tux's controller layer provides a clean, composable interface for database operations. Controllers encapsulate business logic, optimize queries, and provide consistent APIs for database interactions. +## Overview -## Architecture +Tux uses a controller pattern for database operations. All controllers inherit from `BaseController` which provides CRUD, query, pagination, bulk operations, and transaction management. -### BaseController Composition Structure +## Controller Structure -BaseController uses **composition** to provide specialized database operations: - -#### Core Controllers (Eagerly Loaded) - -- **CrudController**: Basic Create, Read, Update, Delete operations -- **QueryController**: Advanced querying with filtering and relationships - -#### Specialized Controllers (Lazy Loaded) - -- **PaginationController**: Paginated results with metadata -- **BulkOperationsController**: Batch operations for efficiency -- **TransactionController**: Transaction management -- **UpsertController**: Get-or-create and upsert patterns -- **PerformanceController**: Query optimization and analysis - -### Lazy Initialization Strategy - -Specialized controllers load **on-demand** to: - -- Reduce memory usage -- Improve startup speed -- Maintain flexibility for adding new controller types - -### DatabaseCoordinator Organization - -DatabaseCoordinator provides **centralized controller access** through facade pattern: - -- Uniform property-based access -- Lazy loading of model-specific controllers -- Single entry point for all database operations - -## Usage Patterns - -### DatabaseCoordinator Usage - -**Access model-specific controllers through centralized coordinator:** - -```python -from tux.database.controllers import DatabaseCoordinator - -coordinator = DatabaseCoordinator(db_service) - -# Lazy-loaded controllers -guild = await coordinator.guild.get_by_id(guild_id) -config = await coordinator.guild_config.get_by_id(config_id) -cases = await coordinator.case.find_all(filters={"guild_id": guild_id}) -``` - -**Benefits:** - -- Single entry point for all controllers -- Consistent API across models -- Lazy loading reduces overhead -- Easy to mock in tests - -### BaseController Usage - -**Create controllers for custom models:** +### Base Controller Pattern ```python from tux.database.controllers.base import BaseController from tux.database.models import MyModel +from tux.database.service import DatabaseService -controller = BaseController(MyModel, db_service) +class MyModelController(BaseController[MyModel]): + """Controller for MyModel operations.""" -# Core CRUD operations -user = await controller.create(name="Alice", email="alice@example.com") -user = await controller.get_by_id(user_id) -user = await controller.update_by_id(user_id, name="Bob") -deleted = await controller.delete_by_id(user_id) - -# Query operations -users = await controller.find_all(filters={"active": True}) -count = await controller.count(filters={"active": True}) -user = await controller.find_one(filters={"email": "alice@example.com"}) + def __init__(self, db: DatabaseService) -> None: + super().__init__(MyModel, db) ``` -### Custom Controller Methods +✅ **GOOD:** Inherits from BaseController, proper typing, DatabaseService injection -**Create domain-specific controllers:** +❌ **BAD:** Direct database access, missing BaseController ```python -from tux.database.controllers.base import BaseController - -class UserController(BaseController[User]): - def __init__(self, db: DatabaseService): - super().__init__(User, db) - - async def get_active_users(self) -> list[User]: - """Get all active users with business logic.""" - return await self.find_all(filters={"active": True, "verified": True}) - - async def promote_user(self, user_id: int) -> User | None: - """Promote user with transaction safety.""" - async def operation(session): - user = await session.get(User, user_id) - if user: - user.role = "admin" - await session.commit() - await session.refresh(user) - return user - - return await self.with_transaction(operation) +# ❌ BAD: Direct session access +class MyModelController: + async def get(self, session: AsyncSession, id: int): + return await session.get(MyModel, id) ``` -### Pagination Patterns +## CRUD Operations -**Use pagination for large result sets:** +### Create ```python -# Simple pagination -result = await controller.paginate(page=1, per_page=20) - -# With filters and ordering -result = await controller.paginate( - page=1, - per_page=20, - filters={"active": True}, - order_by=User.created_at.desc() +# ✅ GOOD: Use controller's create method +controller = MyModelController(db) +new_model = await controller.create( + name="Example", + description="Description" ) -# Access pagination metadata -users = result.items -total = result.total -pages = result.pages -has_next = result.has_next -has_prev = result.has_prev +# Returns created model instance ``` -### Bulk Operations - -**Efficient batch processing:** +❌ **BAD:** Direct session operations ```python -# Bulk create -users = await controller.bulk_create([ - {"name": "Alice", "email": "alice@example.com"}, - {"name": "Bob", "email": "bob@example.com"}, -]) - -# Bulk update -updated_count = await controller.bulk_update([ - (user_id_1, {"active": False}), - (user_id_2, {"active": False}), -]) - -# Bulk delete -deleted_count = await controller.bulk_delete([user_id_1, user_id_2]) - -# Update where -updated = await controller.update_where( - filters={"role": "guest"}, - values={"active": False} -) -``` - -### Upsert Operations - -**Get-or-create patterns:** - -```python -# Upsert by field -user, created = await controller.upsert_by_field( - field_name="email", - field_value="alice@example.com", - defaults={"name": "Alice", "active": True} -) - -# Upsert by ID -user, created = await controller.upsert_by_id( - record_id=user_id, - defaults={"name": "Alice"} -) - -# Get or create -user, created = await controller.get_or_create( - email="alice@example.com", - defaults={"name": "Alice"} -) +# ❌ BAD: Don't access session directly +async with db.session() as session: + model = MyModel(name="Example") + session.add(model) + await session.commit() ``` -### Transaction Management - -**Explicit transaction control:** +### Read ```python -# With session context -async def operation(session): - user = await session.get(User, user_id) - user.balance += 100 - await session.commit() - return user +# Get by ID +model = await controller.get(id=123) -result = await controller.with_session(operation) +# Get by filters +models = await controller.get_many(name="Example") -# With transaction context -result = await controller.with_transaction(operation) +# Get first matching +model = await controller.get_first(name="Example") ``` -### Advanced Querying +✅ **GOOD:** Use controller methods, proper filtering -**Complex queries with relationships:** +### Update ```python -# Find with relationship loading -users = await controller.find_all_with_options( - filters={"active": True}, - load_relationships=["profile", "settings"] +# ✅ GOOD: Use update method +updated = await controller.update( + id=123, + name="New Name", + description="New Description" ) -# JSON column queries -results = await controller.find_with_json_query( - json_column="metadata", - json_path="$.preferences.theme", - value="dark" -) - -# Array contains -results = await controller.find_with_array_contains( - array_column="tags", - value="admin" -) - -# Full-text search -results = await controller.find_with_full_text_search( - search_columns=["name", "email"], - search_term="alice" +# Partial update +updated = await controller.update( + id=123, + name="New Name" + # Only updates name, leaves other fields unchanged ) ``` -## Best Practices - -### Always Use Controllers, Not Direct Session Access - -**Why:** - -- ✅ Type safety through full type checking -- ✅ Business logic enforced at controller level -- ✅ Consistent APIs across the application -- ✅ Easy testability and mocking -- ✅ Isolated changes for maintainability - -**Anti-pattern:** +❌ **BAD:** Manual update logic ```python -# BAD: Direct session access +# ❌ BAD: Manual update async with db.session() as session: - user = await session.get(User, user_id) - # Business logic mixed with data access + model = await session.get(MyModel, 123) + model.name = "New Name" + await session.commit() ``` -**Good pattern:** +### Delete ```python -# GOOD: Controller usage -controller = BaseController(User, db_service) -user = await controller.get_by_id(user_id) -# Business logic in service layer -``` +# ✅ GOOD: Use delete method +deleted = await controller.delete(id=123) -### Create Model-Specific Controllers for Domain Logic - -**Encapsulate business rules:** - -```python -class GuildController(BaseController[Guild]): - async def get_or_create_guild(self, guild_id: int) -> Guild: - """Get existing guild or create with defaults.""" - return await self.get_or_create( - id=guild_id, - defaults={"name": "Unknown Guild"} - ) - - async def get_active_members(self, guild_id: int) -> list[Member]: - """Get active members with business logic.""" - guild = await self.get_by_id(guild_id) - if not guild: - return [] - # Complex business logic here - return await self.find_all(filters={"guild_id": guild_id, "active": True}) +# Soft delete (if model supports it) +deleted = await controller.soft_delete(id=123) ``` -### Use Lazy-Loaded Controllers for Complex Operations +## Query Operations -**Benefits:** - -- Performance optimization through on-demand loading -- Memory efficiency for simple operations -- Faster startup through reduced initialization -- Scalability support for many operation types - -**When to use:** - -- Pagination for large datasets -- Bulk operations for efficiency -- Upsert for synchronization -- Transactions for consistency - -### Leverage Specialized Controllers for Optimized Queries - -**Pagination:** +### Filtering ```python -# For UI display with metadata -result = await controller.paginate(page=1, per_page=20) -``` +# ✅ GOOD: Use query controller methods +models = await controller.query.filter( + name="Example", + status="active" +).all() -**Bulk operations:** - -```python -# For batch processing -await controller.bulk_create(items) +# Complex filters +models = await controller.query.filter( + created_at__gte=datetime.now() - timedelta(days=7) +).all() ``` -**Upsert:** +### Ordering ```python -# For data synchronization -user, created = await controller.upsert_by_field("email", email) -``` +# ✅ GOOD: Use order_by +models = await controller.query.order_by( + "-created_at" # Descending +).all() -**Transactions:** - -```python -# For multi-step operations -await controller.with_transaction(complex_operation) +models = await controller.query.order_by( + "name", # Ascending + "-created_at" # Then descending +).all() ``` -### Handle Errors at Appropriate Levels - -**Controller-level validation:** +### Pagination ```python -async def create_user(self, **kwargs): - # Validate business rules - if await self.exists({"email": kwargs["email"]}): - raise ValueError("Email already exists") +# ✅ GOOD: Use pagination controller +result = await controller.pagination.paginate( + page=1, + per_page=20, + filters={"status": "active"} +) - # Create with validation - return await self.create(**kwargs) +# result.items - List of models +# result.total - Total count +# result.pages - Total pages ``` -### Design Controllers for Testability - -**Use dependency injection:** +## Bulk Operations ```python -class UserService: - def __init__(self, controller: BaseController[User]): - self.controller = controller - - async def get_user(self, user_id: int): - return await self.controller.get_by_id(user_id) - -# Easy to mock in tests -mock_controller = Mock(spec=BaseController) -service = UserService(mock_controller) -``` - -### Use Appropriate Loading Strategies - -**Selective relationship loading:** +# ✅ GOOD: Use bulk controller +await controller.bulk.create_many([ + MyModel(name="Item 1"), + MyModel(name="Item 2"), + MyModel(name="Item 3"), +]) -```python -# Load specific relationships -users = await controller.find_all_with_options( - load_relationships=["profile"] # Only load profile, not all relationships +await controller.bulk.update_many( + filters={"status": "old"}, + updates={"status": "new"} ) -# Avoid over-fetching -# BAD: Loading all relationships when not needed -users = await controller.find_all_with_options( - load_relationships=None # Loads ALL relationships -) +await controller.bulk.delete_many(filters={"status": "inactive"}) ``` -### Document Controller Methods Clearly - -**Comprehensive docstrings:** +## Transactions ```python -async def get_active_users(self, limit: int | None = None) -> list[User]: - """ - Get all active users with optional limit. - - Parameters - ---------- - limit : int | None, optional - Maximum number of users to return. If None, returns all. - - Returns - ------- - list[User] - List of active users. - - Raises - ------ - ValueError - If limit is negative. - """ - if limit is not None and limit < 0: - raise ValueError("Limit must be non-negative") - - return await self.find_all( - filters={"active": True}, - limit=limit - ) +# ✅ GOOD: Use transaction controller +async with controller.transaction.begin(): + model1 = await controller.create(name="Model 1") + model2 = await controller.create(name="Model 2") + # Both committed together, or both rolled back on error ``` -## Anti-Patterns - -### ❌ Bypassing Controllers +❌ **BAD:** Manual transaction management ```python -# BAD: Direct session access +# ❌ BAD: Manual transactions async with db.session() as session: - user = await session.get(User, user_id) + async with session.begin(): + # Manual transaction logic + pass ``` -### ❌ Mixing Business Logic with Data Access +## Controller Composition -```python -# BAD: Business logic in controller -async def create_user(self, **kwargs): - # Validation logic mixed with data access - if len(kwargs["name"]) < 3: - raise ValueError("Name too short") - return await self.create(**kwargs) -``` +BaseController composes specialized controllers: + +- **CrudController** - Basic CRUD operations +- **QueryController** - Query and filtering +- **PaginationController** - Pagination support +- **BulkOperationsController** - Bulk operations +- **TransactionController** - Transaction management +- **PerformanceController** - Performance optimizations +- **UpsertController** - Upsert operations -### ❌ Over-Fetching Relationships +Access via properties: ```python -# BAD: Loading all relationships -users = await controller.find_all_with_options( - load_relationships=None # Loads everything -) +# ✅ GOOD: Access specialized controllers +await controller.crud.create(...) +await controller.query.filter(...) +await controller.pagination.paginate(...) ``` -### ❌ Ignoring Pagination for Large Datasets +## Best Practices -```python -# BAD: Loading all records -users = await controller.find_all() # Could be thousands +1. **Always use BaseController** - Don't access database directly +2. **Inject DatabaseService** - Use dependency injection +3. **Use controller methods** - Don't bypass controller layer +4. **Handle errors properly** - Let controller handle database errors +5. **Use transactions** - For multi-step operations +6. **Use bulk operations** - For multiple records +7. **Type hints** - Always type controller methods -# GOOD: Use pagination -result = await controller.paginate(page=1, per_page=20) -``` +## Anti-Patterns + +1. ❌ **Direct session access** - Always use controllers +2. ❌ **Bypassing controller layer** - Don't access database directly +3. ❌ **Manual transaction management** - Use transaction controller +4. ❌ **Missing error handling** - Let controller handle errors +5. ❌ **No type hints** - Always type controller methods -## Related Rules +## See Also -- [`overview.mdc`](overview.mdc) - Architecture overview -- [`service.mdc`](service.mdc) - Underlying database service -- [`models.mdc`](models.mdc) - Models used by controllers +- @database/models.mdc - Model patterns +- @database/services.mdc - Service layer patterns +- @database/queries.mdc - Query optimization patterns +- @AGENTS.md - General coding standards +- @rules.mdc - Complete catalog of all rules and commands diff --git a/.cursor/rules/database/migrations.mdc b/.cursor/rules/database/migrations.mdc new file mode 100644 index 00000000..056e8c49 --- /dev/null +++ b/.cursor/rules/database/migrations.mdc @@ -0,0 +1,249 @@ +--- +description: Alembic migration patterns, creating migrations, upgrade/downgrade functions, and migration safety for Tux +globs: src/tux/database/migrations/**/*.py, alembic.ini +alwaysApply: false +--- + +# Alembic Database Migrations + +## Overview + +Tux uses Alembic for database schema migrations with async PostgreSQL support. Migrations are automatically generated from SQLModel changes and must include both upgrade and downgrade functions. + +## Migration Creation + +### Creating a New Migration + +```bash +# ✅ GOOD: Use the db script command +uv run db migrate-dev "description of changes" + +# This creates a new migration file in: +# src/tux/database/migrations/versions/ +``` + +❌ **BAD:** Manually creating migration files or using alembic directly + +```bash +# ❌ BAD: Don't use alembic directly +alembic revision --autogenerate -m "description" +``` + +### Migration File Structure + +```python +"""Revision ID: abc123 +Revises: def456 +Create Date: 2025-01-15 10:30:00 +""" +from __future__ import annotations + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +import sqlmodel + +# revision identifiers +revision: str = "abc123" +down_revision: Union[str, None] = "def456" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ✅ GOOD: Always implement upgrade + op.create_table( + "my_table", + sa.Column("id", sa.BigInteger(), nullable=False), + sa.Column("name", sa.String(255), nullable=False), + sa.PrimaryKeyConstraint("id"), + ) + + +def downgrade() -> None: + # ✅ GOOD: Always implement downgrade for rollback + op.drop_table("my_table") +``` + +✅ **GOOD:** Both upgrade and downgrade implemented, proper imports + +❌ **BAD:** Missing downgrade, incomplete migration + +## Migration Patterns + +### Creating Tables + +```python +def upgrade() -> None: + op.create_table( + "my_table", + sa.Column("id", sa.BigInteger(), nullable=False), + sa.Column("name", sa.String(255), nullable=False), + sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("CURRENT_TIMESTAMP")), + sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("CURRENT_TIMESTAMP")), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index("ix_my_table_name", "my_table", ["name"]) + +def downgrade() -> None: + op.drop_index("ix_my_table_name", table_name="my_table") + op.drop_table("my_table") +``` + +✅ **GOOD:** Includes indexes, timestamps, proper constraints + +### Adding Columns + +```python +def upgrade() -> None: + op.add_column("my_table", sa.Column("description", sa.String(500), nullable=True)) + +def downgrade() -> None: + op.drop_column("my_table", "description") +``` + +✅ **GOOD:** Symmetric upgrade/downgrade + +❌ **BAD:** Missing downgrade + +```python +# ❌ BAD: No way to rollback +def upgrade() -> None: + op.add_column("my_table", sa.Column("description", sa.String(500))) + +def downgrade() -> None: + pass # Missing rollback! +``` + +### Modifying Columns + +```python +def upgrade() -> None: + op.alter_column( + "my_table", + "name", + existing_type=sa.String(255), + type_=sa.String(500), + existing_nullable=False, + nullable=False, + ) + +def downgrade() -> None: + op.alter_column( + "my_table", + "name", + existing_type=sa.String(500), + type_=sa.String(255), + existing_nullable=False, + nullable=False, + ) +``` + +✅ **GOOD:** Specifies existing_type and type_ for safety + +### Adding Foreign Keys + +```python +def upgrade() -> None: + op.add_column("cases", sa.Column("guild_id", sa.BigInteger(), nullable=False)) + op.create_foreign_key( + "fk_cases_guild_id_guild", + "cases", + "guild", + ["guild_id"], + ["id"], + ondelete="CASCADE", + ) + +def downgrade() -> None: + op.drop_constraint("fk_cases_guild_id_guild", "cases", type_="foreignkey") + op.drop_column("cases", "guild_id") +``` + +✅ **GOOD:** Named constraint, proper cascade options + +### Creating Enums + +```python +def upgrade() -> None: + # Create enum type + case_type_enum = sa.Enum("WARN", "TIMEOUT", "KICK", "BAN", name="case_type") + case_type_enum.create(op.get_bind(), checkfirst=True) + + # Use enum in column + op.add_column("cases", sa.Column("type", case_type_enum, nullable=False)) + +def downgrade() -> None: + op.drop_column("cases", "type") + op.execute("DROP TYPE IF EXISTS case_type") +``` + +✅ **GOOD:** Creates enum type, uses checkfirst=True + +## Migration Safety + +### Data Migrations + +For data migrations, use batch operations: + +```python +def upgrade() -> None: + # ✅ GOOD: Use batch operations for large data changes + with op.batch_alter_table("my_table", schema=None) as batch_op: + batch_op.alter_column("status", type_=sa.String(50)) + +def downgrade() -> None: + with op.batch_alter_table("my_table", schema=None) as batch_op: + batch_op.alter_column("status", type_=sa.String(20)) +``` + +### Testing Migrations + +Always test both upgrade and downgrade: + +```bash +# ✅ GOOD: Test migration up and down +uv run db migrate-dev "test migration" +uv run db reset # Test full migration chain +``` + +❌ **BAD:** Not testing downgrade + +## Migration Environment + +The migration environment (`env.py`) handles: + +- **URL Conversion:** Async to sync URL conversion +- **Retry Logic:** 5 attempts with exponential backoff +- **Connection Testing:** Validates connectivity before migrations +- **Empty Migration Prevention:** Prevents empty migrations +- **Transaction Safety:** Each migration in its own transaction + +✅ **GOOD:** Environment configured with safety features + +## Best Practices + +1. **Always implement downgrade** - Required for rollback capability +2. **Test migrations** - Test both upgrade and downgrade paths +3. **Use named constraints** - Makes migrations easier to understand +4. **Include indexes** - Don't forget to create/drop indexes +5. **Handle enums properly** - Create/drop enum types correctly +6. **Use batch operations** - For large table modifications +7. **Document complex migrations** - Add comments for non-obvious changes + +## Anti-Patterns + +1. ❌ **Missing downgrade** - Always implement rollback +2. ❌ **Unnamed constraints** - Always name constraints +3. ❌ **Missing indexes** - Don't forget index operations +4. ❌ **Direct alembic usage** - Use `uv run db migrate-dev` +5. ❌ **Not testing downgrade** - Always test rollback +6. ❌ **Empty migrations** - Prevented by env.py, but check manually + +## See Also + +- @database/models.mdc - Model patterns that drive migrations +- @database/controllers.mdc - Controller patterns +- @AGENTS.md - General coding standards +- @rules.mdc - Complete catalog of all rules and commands diff --git a/.cursor/rules/database/models.mdc b/.cursor/rules/database/models.mdc index 86dc76eb..717a27cc 100644 --- a/.cursor/rules/database/models.mdc +++ b/.cursor/rules/database/models.mdc @@ -1,513 +1,260 @@ --- -alwaysApply: true -description: Database model patterns, BaseModel usage, and SQLModel best practices +description: SQLModel database model patterns, BaseModel usage, relationships, and field definitions for Tux +globs: src/tux/database/models/**/*.py, **/*model*.py +alwaysApply: false --- -# Database Model Rules +# SQLModel Database Models -Tux uses SQLModel for type-safe database models that combine SQLAlchemy and Pydantic. All models inherit from a custom BaseModel class providing automatic timestamp management, serialization utilities, and PostgreSQL-specific features. +## Overview -## BaseModel Foundation +Tux uses SQLModel (SQLAlchemy + Pydantic) for type-safe database models with automatic validation and serialization. All models inherit from `BaseModel` which provides automatic timestamp management. -### Always Inherit from BaseModel +## Base Model Pattern -**All Tux models must inherit from BaseModel:** +All database models must inherit from `BaseModel`: ```python -from tux.database.models.base import BaseModel +from tux.database.models import BaseModel -class User(BaseModel, table=True): - id: int = Field(primary_key=True) - name: str - email: str = Field(unique=True, index=True) -``` - -**Benefits:** - -- Automatic created_at and updated_at timestamps -- Built-in serialization utilities -- Consistent behavior across all models -- Future-proofing through centralized features - -### BaseModel Features - -**Automatic Timestamps:** - -```python -class User(BaseModel, table=True): - # created_at and updated_at are automatically added - # Managed by database with server_default and onupdate - id: int = Field(primary_key=True) - name: str -``` - -**Serialization:** - -```python -user = User(id=1, name="Alice") -data = user.to_dict() # Converts to dict with ISO datetime strings -# {'id': 1, 'name': 'Alice', 'created_at': '2024-01-01T00:00:00', ...} -``` - -**Relationship Support:** - -```python -data = user.to_dict(include_relationships=True, relationships=["profile"]) -# Includes related profile data -``` - -## Mixin Patterns - -### UUIDMixin - -**For records needing UUID primary keys:** +class MyModel(BaseModel, table=True): + """Model description with NumPy docstring format.""" -```python -from tux.database.models.base import UUIDMixin, BaseModel - -class ApiKey(BaseModel, UUIDMixin, table=True): - key: str = Field(unique=True, index=True) - user_id: int = Field(foreign_key="user.id") - # id field is automatically UUID with auto-generation + id: int = Field( + primary_key=True, + sa_type=BigInteger, + description="Primary key description" + ) ``` -**Use Cases:** - -- API keys and tokens -- External-facing identifiers -- Records needing non-sequential IDs +✅ **GOOD:** Inherits from BaseModel, includes docstring, proper field definitions -### SoftDeleteMixin - -**For recoverable data:** +❌ **BAD:** Direct SQLModel inheritance without BaseModel, missing docstrings ```python -from tux.database.models.base import SoftDeleteMixin, BaseModel - -class User(BaseModel, SoftDeleteMixin, table=True): +# ❌ BAD: Missing BaseModel, no docstring +class MyModel(SQLModel, table=True): id: int = Field(primary_key=True) - name: str - - # Soft delete methods available - # user.soft_delete() - Marks as deleted - # user.restore() - Restores deleted record -``` - -**Use Cases:** - -- Users and accounts -- Important records that should be recoverable -- Audit trail requirements - -**Combining Mixins:** - -```python -class ImportantRecord(BaseModel, UUIDMixin, SoftDeleteMixin, table=True): - # Has UUID id, soft delete, and timestamps - data: str ``` -## Model Definition Patterns +## Field Definitions -### Enum Definitions +### Primary Keys -**Type-safe constants for database fields:** +**Integer IDs (Discord entities):** ```python -from enum import Enum - -class UserRole(str, Enum): - ADMIN = "admin" - MODERATOR = "moderator" - USER = "user" - -class User(BaseModel, table=True): - id: int = Field(primary_key=True) - role: UserRole = Field(default=UserRole.USER) +id: int = Field( + primary_key=True, + sa_type=BigInteger, + description="Discord guild ID" +) ``` -**Benefits:** - -- Compile-time validation -- Self-documenting names -- Type safety in Python code -- Stored as strings in PostgreSQL - -### Relationship Definitions - -**Proper relationship configuration:** +**UUID IDs (internal entities):** ```python -from sqlmodel import Relationship - -class User(BaseModel, table=True): - id: int = Field(primary_key=True) - name: str - - # One-to-many relationship - cases: list["Case"] = Relationship(back_populates="user") +from tux.database.models import UUIDMixin -class Case(BaseModel, table=True): - id: int = Field(primary_key=True) - user_id: int = Field(foreign_key="user.id") - - # Many-to-one relationship - user: User = Relationship(back_populates="cases") +class MyModel(BaseModel, UUIDMixin, table=True): + # id is automatically provided by UUIDMixin + pass ``` -**Relationship Best Practices:** +✅ **GOOD:** Appropriate ID type for entity type, proper sa_type -- Always use `Relationship` for navigation -- Configure `back_populates` for bidirectional relationships -- Choose appropriate `lazy` strategy (selectin, joined, noload) -- Set proper `cascade` behavior for deletions +❌ **BAD:** Wrong ID type, missing sa_type for BigInteger -### Field Definitions +### Timestamps -**Proper field configuration:** +Timestamps are automatically managed by `BaseModel`: ```python -class User(BaseModel, table=True): - # Primary key - id: int = Field(primary_key=True) - - # Required field +# ✅ GOOD: BaseModel provides created_at and updated_at automatically +class MyModel(BaseModel, table=True): name: str - # Optional field - email: str | None = None - - # Field with constraints - age: int = Field(ge=0, le=150) - - # Indexed field - username: str = Field(unique=True, index=True) - - # Foreign key - guild_id: int = Field(foreign_key="guild.id") - - # JSON field - metadata: dict[str, Any] = Field(default_factory=dict, sa_type=JSON) - - # Array field - tags: list[str] = Field(default_factory=list, sa_type=ARRAY(String)) +# Timestamps are database-managed: +# - created_at: Set on insert +# - updated_at: Updated on every update ``` -### PostgreSQL-Specific Types - -**Leverage PostgreSQL features:** +❌ **BAD:** Manually defining timestamp fields ```python -from sqlalchemy import JSON, ARRAY, String - -class User(BaseModel, table=True): - # JSONB for flexible metadata - preferences: dict[str, Any] = Field( - default_factory=dict, - sa_type=JSON - ) - - # Array for ordered lists - permissions: list[str] = Field( - default_factory=list, - sa_type=ARRAY(String) - ) - - # Enum stored as string - status: UserStatus = Field(default=UserStatus.ACTIVE) +# ❌ BAD: Don't manually define timestamps +class MyModel(BaseModel, table=True): + created_at: datetime = Field(...) # Already in BaseModel! ``` -## Best Practices - -### Always Inherit from BaseModel - -**Ensures uniform behavior:** +### Field Types and Constraints ```python -# GOOD: Inherits from BaseModel -class User(BaseModel, table=True): - id: int = Field(primary_key=True) - name: str - -# BAD: Direct SQLModel inheritance -class User(SQLModel, table=True): - id: int = Field(primary_key=True) - name: str - # Missing automatic timestamps and serialization -``` - -### Use Appropriate Mixins - -**Choose mixins based on requirements:** - -```python -# For UUID primary keys -class ApiKey(BaseModel, UUIDMixin, table=True): - key: str - -# For soft delete functionality -class User(BaseModel, SoftDeleteMixin, table=True): - name: str - -# Combine as needed -class ImportantRecord(BaseModel, UUIDMixin, SoftDeleteMixin, table=True): - data: str -``` - -### Define Relationships Carefully - -**Proper relationship configuration:** - -```python -# GOOD: Proper bidirectional relationship -class User(BaseModel, table=True): - cases: list["Case"] = Relationship(back_populates="user") - -class Case(BaseModel, table=True): - user: User = Relationship(back_populates="cases") - -# BAD: Missing back_populates -class User(BaseModel, table=True): - cases: list["Case"] = Relationship() # No back_populates -``` - -**Cascade Behavior:** +# String fields +name: str = Field( + max_length=255, + description="Human-readable name" +) -```python -# Appropriate cascade for parent-child relationships -class Guild(BaseModel, table=True): - configs: list["GuildConfig"] = Relationship( - back_populates="guild", - sa_relationship_kwargs={"cascade": "all, delete-orphan"} - ) -``` +# Optional fields +description: str | None = Field( + default=None, + nullable=True, + description="Optional description" +) -**Lazy Loading Strategy:** +# Integer with constraints +count: int = Field( + default=0, + ge=0, # Greater than or equal to 0 + description="Non-negative count" +) -```python -# selectin: Separate query (default, good for most cases) -cases: list["Case"] = Relationship( - back_populates="user", - sa_relationship_kwargs={"lazy": "selectin"} +# Enum fields +status: CaseType = Field( + sa_type=PgEnum(CaseType, name="case_type"), + description="Case type enum" ) -# joined: Single query with JOIN (performance-critical) -user: User = Relationship( - back_populates="cases", - sa_relationship_kwargs={"lazy": "joined"} +# JSON fields +metadata: dict[str, Any] = Field( + default_factory=dict, + sa_type=JSON, + description="JSON metadata" ) -# noload: Skip loading (explicit control) -metadata: dict = Relationship( - sa_relationship_kwargs={"lazy": "noload"} +# DateTime with timezone +created_at: datetime | None = Field( + sa_type=DateTime(timezone=True), + description="Creation timestamp" ) ``` -### Use Type Hints Consistently +✅ **GOOD:** Proper types, constraints, descriptions, nullable handling -**Proper type annotations:** +❌ **BAD:** Missing constraints, wrong types, no descriptions -```python -# GOOD: Clear type hints -class User(BaseModel, table=True): - id: int = Field(primary_key=True) - name: str - email: str | None = None - roles: list[str] = Field(default_factory=list) - metadata: dict[str, Any] = Field(default_factory=dict) - -# BAD: Missing type hints -class User(BaseModel, table=True): - id = Field(primary_key=True) # No type hint - name = Field() # No type hint -``` +## Relationships -**Use `| None` convention:** +### One-to-Many ```python -# GOOD: Modern type union syntax -email: str | None = None +from sqlalchemy.orm import Mapped, relationship -# BAD: Optional type (deprecated) -from typing import Optional -email: Optional[str] = None -``` - -### Leverage PostgreSQL Features - -**JSONB for flexible metadata:** +class Guild(BaseModel, table=True): + id: int = Field(primary_key=True, sa_type=BigInteger) -```python -class User(BaseModel, table=True): - # JSONB allows querying and indexing - preferences: dict[str, Any] = Field( - default_factory=dict, - sa_type=JSON + # Relationship definition + cases: Mapped[list["Case"]] = relationship( + "Case", + back_populates="guild", + cascade="all, delete-orphan" ) - # Query JSONB fields - # await controller.find_with_json_query("preferences", "$.theme", "dark") -``` - -**Arrays for ordered lists:** - -```python -class User(BaseModel, table=True): - # Array type for ordered lists - tags: list[str] = Field( - default_factory=list, - sa_type=ARRAY(String) +class Case(BaseModel, table=True): + guild_id: int = Field( + foreign_key="guild.id", + sa_type=BigInteger, + description="Foreign key to guild" ) - # Query array fields - # await controller.find_with_array_contains("tags", "admin") + guild: Mapped["Guild"] = relationship( + "Guild", + back_populates="cases" + ) ``` -**Enums for constrained choices:** +✅ **GOOD:** Proper Mapped typing, back_populates, cascade options -```python -class User(BaseModel, table=True): - # Enum stored as string in database - role: UserRole = Field(default=UserRole.USER) -``` +❌ **BAD:** Missing Mapped, no back_populates, wrong cascade -### Handle Serialization Properly +### Many-to-Many -**Use to_dict() for API responses:** +Use association tables for many-to-many relationships: ```python -# Basic serialization -user = User(id=1, name="Alice") -data = user.to_dict() -# {'id': 1, 'name': 'Alice', 'created_at': '...', 'updated_at': '...'} +class PermissionAssignment(BaseModel, table=True): + rank_id: UUID = Field(foreign_key="permission_rank.id") + command_id: UUID = Field(foreign_key="permission_command.id") -# With relationships -data = user.to_dict(include_relationships=True, relationships=["profile"]) -# Includes profile relationship data - -# Without relationships (default) -data = user.to_dict() # Relationships excluded + # Composite primary key + __table_args__ = ( + UniqueConstraint("rank_id", "command_id"), + ) ``` -**Serialization Guidelines:** +## Model Organization -- Use `to_dict()` for logging, debugging, and API responses -- Control relationship inclusion to prevent over-fetching -- Ensure proper type conversion for JSON compatibility -- Handle circular references with relationship control +### File Structure -### Index Strategically +```text +src/tux/database/models/ +├── __init__.py # Exports all models +├── base.py # BaseModel, mixins +├── enums.py # Database enums +└── models.py # All model definitions +``` -**Add indexes for frequently queried fields:** +### Import Pattern ```python -class User(BaseModel, table=True): - # Indexed unique field - email: str = Field(unique=True, index=True) +# ✅ GOOD: Import from models package +from tux.database.models import Guild, Case, BaseModel - # Indexed foreign key - guild_id: int = Field(foreign_key="guild.id", index=True) - - # Composite index (via migration) - # CREATE INDEX idx_user_guild_active ON user(guild_id, active) +# ❌ BAD: Direct imports from files +from tux.database.models.models import Guild ``` -**Index Guidelines:** - -- Index foreign keys -- Index frequently queried fields -- Use GIN indexes for JSON and array fields -- Consider query patterns when adding indexes -- Don't over-index (slows writes) +## Validation and Serialization -### Document Model Purpose +### Pydantic Validation -**Clear docstrings:** +SQLModel models automatically validate on creation: ```python -class User(BaseModel, table=True): - """ - User model representing a Discord user. - - Stores user information including name, email, and preferences. - Supports soft delete for account recovery. - - Attributes - ---------- - id : int - Primary key identifier. - name : str - User's display name. - email : str | None - User's email address (optional). - active : bool - Whether the user account is active. - - Relationships - -------------- - cases : list[Case] - Moderation cases associated with this user. - """ - id: int = Field(primary_key=True) - name: str - email: str | None = None - active: bool = Field(default=True) +# ✅ GOOD: Validation happens automatically +guild = Guild(id=123456789, case_count=0) - cases: list["Case"] = Relationship(back_populates="user") +# ❌ BAD: Invalid data raises ValidationError +guild = Guild(id=-1) # Fails if ge=0 constraint exists ``` -## Anti-Patterns +### Serialization -### ❌ Not Inheriting from BaseModel +Use `to_dict()` method from BaseModel: ```python -# BAD: Missing BaseModel -class User(SQLModel, table=True): - id: int = Field(primary_key=True) - # Missing timestamps and serialization -``` - -### ❌ Missing Type Hints +# ✅ GOOD: Use built-in serialization +guild_dict = guild.to_dict() +guild_dict_with_rels = guild.to_dict(include_relationships=True) -```python -# BAD: No type hints -class User(BaseModel, table=True): - id = Field(primary_key=True) - name = Field() +# ❌ BAD: Manual dict conversion +guild_dict = {"id": guild.id, "name": guild.name} # Misses timestamps ``` -### ❌ Incorrect Relationship Configuration - -```python -# BAD: Missing back_populates -class User(BaseModel, table=True): - cases: list["Case"] = Relationship() # No back_populates -``` - -### ❌ Over-Fetching in Serialization - -```python -# BAD: Loading all relationships -data = user.to_dict(include_relationships=True) # Loads everything - -# GOOD: Selective loading -data = user.to_dict(include_relationships=True, relationships=["profile"]) -``` +## Best Practices -### ❌ Missing Indexes on Foreign Keys +1. **Always inherit from BaseModel** for automatic timestamps +2. **Use appropriate ID types**: BigInteger for Discord IDs, UUID for internal +3. **Add constraints**: Use `ge`, `le`, `max_length` where appropriate +4. **Document fields**: Include description in Field() definitions +5. **Use Mapped[] for relationships**: Type-safe relationship definitions +6. **Validate with Pydantic**: Let SQLModel handle validation +7. **Use to_dict() for serialization**: Don't manually convert to dict -```python -# BAD: No index on foreign key -class Case(BaseModel, table=True): - user_id: int = Field(foreign_key="user.id") # No index +## Anti-Patterns -# GOOD: Indexed foreign key -class Case(BaseModel, table=True): - user_id: int = Field(foreign_key="user.id", index=True) -``` +1. ❌ **Direct SQLModel inheritance** - Always use BaseModel +2. ❌ **Manual timestamp fields** - BaseModel provides them +3. ❌ **Missing field descriptions** - Always document fields +4. ❌ **Wrong ID types** - Use BigInteger for Discord IDs +5. ❌ **Missing constraints** - Add validation constraints +6. ❌ **Manual serialization** - Use to_dict() method -## Related Rules +## See Also -- [`overview.mdc`](overview.mdc) - Architecture overview -- [`controllers.mdc`](controllers.mdc) - Using models through controllers -- [`service.mdc`](service.mdc) - Service layer using models +- @database/migrations.mdc - Migration patterns +- @database/controllers.mdc - Controller patterns +- @database/services.mdc - Service layer patterns +- @AGENTS.md - General coding standards +- @rules.mdc - Complete catalog of all rules and commands diff --git a/.cursor/rules/database/overview.mdc b/.cursor/rules/database/overview.mdc deleted file mode 100644 index 9ad6f517..00000000 --- a/.cursor/rules/database/overview.mdc +++ /dev/null @@ -1,180 +0,0 @@ ---- -alwaysApply: true -description: Database architecture overview and core principles for Tux's three-layer database system ---- - -# Database Architecture Overview - -Tux uses a **three-layer database architecture** that separates concerns while maintaining type safety and developer experience. The architecture prioritizes async-first design, automatic resource management, and composable operations built on PostgreSQL. - -## Three-Layer Architecture - -### Service Layer - -**Foundation layer** handling all PostgreSQL interactions with: - -- Connection pooling with retry logic -- Session management with automatic cleanup -- Health monitoring and validation -- Transaction handling with automatic rollback - -**Location:** `src/tux/database/service.py` - -### Controller Layer - -**Business logic layer** providing composable database operations through: - -- BaseController with lazy-loaded specialized controllers -- Model-specific controllers (GuildController, CaseController, etc.) -- DatabaseCoordinator facade for centralized access - -**Location:** `src/tux/database/controllers/` - -### Model Layer - -**Type-safe data models** with: - -- Automatic timestamp management (created_at, updated_at) -- Relationship definitions with proper cascade behavior -- PostgreSQL-specific type support (JSONB, arrays, enums) -- Serialization utilities for API responses - -**Location:** `src/tux/database/models/` - -## Core Principles - -### Async-First Design - -- All database operations are async by default -- Non-blocking I/O for efficient concurrent request handling -- Optimal resource utilization for Discord bot workloads - -### Automatic Resource Management - -- Sessions and connections managed through context managers -- Proper cleanup and resource leak prevention -- Automatic commit/rollback on success/failure - -### Composition Over Inheritance - -- Controllers use composition patterns for flexibility -- Lazy-loaded specialized controllers reduce overhead -- Can combine controllers as needed without forcing all functionality - -### Transaction Safety - -- Transactions automatically managed at session level -- All operations within session context are transactional -- Auto-committed on success, rolled back on failure - -### Connection Pooling - -- PostgreSQL connections efficiently pooled -- Pre-ping validation and periodic recycling -- Size management optimized for Discord bot workloads - -## Layer Interaction Flow - -```text -Commands/Interactions - ↓ -DatabaseCoordinator (facade) - ↓ -BaseController (composition) - ↓ -Specialized Controllers (lazy-loaded) - ↓ -DatabaseService (connection management) - ↓ -PostgreSQL -``` - -## Service Access Patterns - -### Bot Attachment - -Database services attached directly to bot instances: - -```python -bot.db_service # DatabaseService instance -bot.db # DatabaseCoordinator instance -``` - -### Context Resolution - -Services automatically discovered from Discord contexts: - -```python -from tux.database.utils import get_db_service_from, get_db_controller_from - -# From interaction or context -db_service = get_db_service_from(interaction) -coordinator = get_db_controller_from(interaction) -``` - -### Fallback Support - -Graceful degradation when preferred access methods unavailable with logging and migration support. - -## When to Use Each Layer - -### Controller Layer - -- ✅ Standard CRUD operations -- ✅ Business logic with relationships -- ✅ Pagination and filtering -- ✅ Bulk operations -- ✅ Complex queries with joins - -### Service Layer - -- ✅ Raw SQL queries -- ✅ Performance-critical operations -- ✅ Health checks and monitoring -- ✅ Custom transaction management -- ✅ Direct connection access - -### Model Layer - -- ✅ Data validation and serialization -- ✅ Relationship definitions -- ✅ Schema definitions -- ✅ Type safety - -## Best Practices - -### Always Use Controllers for Business Logic - -- Never bypass controllers for standard CRUD operations -- Use controllers for type safety and consistency -- Controllers enforce business rules and validation - -### Use Service Layer Sparingly - -- Only for raw SQL or performance-critical paths -- Health checks and monitoring -- Custom transaction management - -### Leverage Model Features - -- Use BaseModel for automatic timestamps -- Use mixins (UUIDMixin, SoftDeleteMixin) for common patterns -- Define relationships with proper cascade behavior - -### Error Handling Strategy - -- **Controller Level**: Business logic errors and validation -- **Service Level**: Connection errors and transaction failures -- **Global Level**: Unexpected errors with monitoring integration - -### Testing Strategy - -- **Unit Tests**: Test controllers with mocked service layer -- **Integration Tests**: Test full stack with real database -- **Isolation**: Each test uses fresh database schema - -## Related Rules - -- [`service.mdc`](service.mdc) - DatabaseService patterns and usage -- [`controllers.mdc`](controllers.mdc) - Controller patterns and composition -- [`models.mdc`](models.mdc) - Model definition and best practices diff --git a/.cursor/rules/database/queries.mdc b/.cursor/rules/database/queries.mdc new file mode 100644 index 00000000..aae3ee3c --- /dev/null +++ b/.cursor/rules/database/queries.mdc @@ -0,0 +1,208 @@ +--- +description: Database query patterns, optimization, filtering, joins, and performance best practices for Tux +globs: src/tux/database/**/*.py +alwaysApply: false +--- + +# Database Query Patterns + +## Overview + +Tux uses SQLAlchemy async queries with SQLModel for type-safe database operations. Queries should be optimized, use proper filtering, and avoid N+1 problems. + +## Basic Query Patterns + +### Select Queries + +```python +from sqlalchemy import select +from sqlmodel import SQLModel + +# ✅ GOOD: Use select() with async session +async with db.session() as session: + stmt = select(MyModel).where(MyModel.status == "active") + result = await session.execute(stmt) + models = result.scalars().all() +``` + +❌ **BAD:** Using deprecated query API + +```python +# ❌ BAD: Old query API (not available in async) +models = await session.query(MyModel).all() # Doesn't exist! +``` + +### Filtering + +```python +# ✅ GOOD: Use where() for filtering +stmt = select(MyModel).where( + MyModel.status == "active", + MyModel.created_at >= datetime.now() - timedelta(days=7) +) + +# Multiple conditions +stmt = select(MyModel).where( + (MyModel.status == "active") | (MyModel.status == "pending") +) +``` + +### Ordering + +```python +# ✅ GOOD: Use order_by() +from sqlalchemy import desc + +stmt = select(MyModel).order_by( + desc(MyModel.created_at), # Newest first + MyModel.name # Then by name +) +``` + +## Relationship Queries + +### Eager Loading + +```python +# ✅ GOOD: Use selectinload or joinedload to avoid N+1 +from sqlalchemy.orm import selectinload + +stmt = select(Guild).options( + selectinload(Guild.cases) # Load cases in one query +) +result = await session.execute(stmt) +guilds = result.scalars().all() + +# Access relationships without additional queries +for guild in guilds: + print(guild.cases) # Already loaded! +``` + +❌ **BAD:** Lazy loading causing N+1 queries + +```python +# ❌ BAD: Lazy loading (N+1 problem) +guilds = await session.execute(select(Guild)).scalars().all() +for guild in guilds: + cases = guild.cases # Additional query per guild! +``` + +### Joins + +```python +# ✅ GOOD: Use joins for filtering related data +from sqlalchemy.orm import joinedload + +stmt = select(Case).join(Guild).where( + Guild.id == 123456789 +).options(joinedload(Case.guild)) + +result = await session.execute(stmt) +cases = result.scalars().all() +``` + +## Query Optimization + +### Indexes + +Always use indexes for frequently queried columns: + +```python +# ✅ GOOD: Model defines indexes +class MyModel(BaseModel, table=True): + name: str = Field(index=True) # Indexed for fast lookups + status: str = Field(index=True) # Indexed for filtering +``` + +❌ **BAD:** Missing indexes on frequently queried columns + +### Limit and Offset + +```python +# ✅ GOOD: Use limit() and offset() for pagination +stmt = select(MyModel).limit(20).offset(0) +``` + +For better performance, use cursor-based pagination: + +```python +# ✅ GOOD: Cursor-based pagination (better performance) +stmt = select(MyModel).where( + MyModel.id > last_id +).order_by(MyModel.id).limit(20) +``` + +### Count Queries + +```python +# ✅ GOOD: Use count() for counting +from sqlalchemy import func + +stmt = select(func.count(MyModel.id)).where(MyModel.status == "active") +result = await session.execute(stmt) +count = result.scalar() +``` + +❌ **BAD:** Loading all records to count + +```python +# ❌ BAD: Loading all records +models = await session.execute(select(MyModel)).scalars().all() +count = len(models) # Loads all records into memory! +``` + +## Aggregations + +```python +# ✅ GOOD: Use SQL aggregations +from sqlalchemy import func + +stmt = select( + func.count(MyModel.id), + func.avg(MyModel.value), + func.max(MyModel.created_at) +).where(MyModel.status == "active") + +result = await session.execute(stmt) +count, avg_value, max_date = result.one() +``` + +## Subqueries + +```python +# ✅ GOOD: Use subqueries for complex queries +subquery = select(func.count(Case.id)).where( + Case.guild_id == Guild.id +).scalar_subquery() + +stmt = select( + Guild, + subquery.label("case_count") +) +``` + +## Best Practices + +1. **Use select()** - Always use select() for queries +2. **Eager load relationships** - Use selectinload/joinedload to avoid N+1 +3. **Add indexes** - Index frequently queried columns +4. **Use limit/offset** - Don't load all records +5. **Use aggregations** - Let database do aggregations +6. **Optimize joins** - Use appropriate join types +7. **Avoid N+1 queries** - Always eager load relationships + +## Anti-Patterns + +1. ❌ **N+1 queries** - Always eager load relationships +2. ❌ **Loading all records** - Use limit/offset +3. ❌ **Missing indexes** - Index frequently queried columns +4. ❌ **Manual counting** - Use SQL count() +5. ❌ **Lazy loading** - Use eager loading +6. ❌ **Complex Python filtering** - Filter in SQL + +## See Also + +- @database/controllers.mdc - Controller query patterns +- @database/models.mdc - Model patterns with relationships +- @AGENTS.md - General coding standards +- @rules.mdc - Complete catalog of all rules and commands diff --git a/.cursor/rules/database/service.mdc b/.cursor/rules/database/service.mdc deleted file mode 100644 index fe1614fa..00000000 --- a/.cursor/rules/database/service.mdc +++ /dev/null @@ -1,241 +0,0 @@ ---- -alwaysApply: true -description: DatabaseService patterns, connection management, and session handling rules ---- - -# Database Service Rules - -The DatabaseService is the foundation layer for PostgreSQL database operations, providing robust connection management, session handling, health monitoring, and error recovery. - -## Core Responsibilities - -### Connection Lifecycle Management - -- Initialize async engine with connection pooling -- Handle connection state (disconnected, connected, error) -- Automatic reconnection with retry logic -- Graceful shutdown and resource cleanup - -### Session Factory Pattern - -- Create sessions through factory pattern for consistency -- Proper async session configuration -- Automatic transaction handling -- Context manager support for resource safety - -### Retry Logic Implementation - -- Automatic retry with exponential backoff -- Handle transient database failures -- Docker container startup delays -- Network resilience - -## Key Patterns - -### Session Context Managers - -**Always use context managers for session management:** - -```python -async with db.session() as session: - result = await session.execute(select(User)) - users = result.scalars().all() - # Automatic commit on success, rollback on exception -``` - -**Benefits:** - -- Automatic resource cleanup -- Proper transaction handling -- Exception safety -- No manual commit/rollback needed - -### Connection Pooling Configuration - -```python -engine = create_async_engine( - database_url, - pool_pre_ping=True, # Validate connections before use - pool_recycle=3600, # Recycle connections after 1 hour - echo=False, # SQL query logging (debug only) -) -``` - -**Configuration Guidelines:** - -- Always enable `pool_pre_ping` for connection validation -- Set `pool_recycle` to prevent stale connections -- Use `echo=True` only for debugging (never in production) -- Configure pool size based on workload (default is usually fine) - -### Health Checks - -**Implement health checks for monitoring:** - -```python -health = await db.health_check() -# Returns: {'status': 'healthy', 'mode': 'async'} -# Or: {'status': 'unhealthy', 'error': '...'} -``` - -**Use Cases:** - -- Startup validation -- Periodic health monitoring -- Error recovery detection -- Load balancer health endpoints - -### Retry Logic with Exponential Backoff - -**Automatic retry for transient failures:** - -```python -result = await db.execute_query( - operation=lambda session: session.execute(query), - span_desc="fetch_users" -) -``` - -**Retry Behavior:** - -- Max 3 retries by default -- Exponential backoff: 0.5s, 1s, 2s -- Retries on: DisconnectionError, TimeoutError, OperationalError -- Raises exception after all retries exhausted - -### Sentry Integration - -**Performance monitoring and error tracking:** - -- Automatic span creation for database operations -- Error context and attempt tracking -- Status updates for observability -- Query performance metrics - -## Usage Patterns - -### Basic Session Usage - -```python -async with db.session() as session: - user = User(name="Alice") - session.add(user) - # Auto-committed on exit -``` - -### Transaction Management - -```python -async with db.session() as session: - async with session.begin(): - # Explicit transaction boundary - await session.execute(update(User).values(name="Bob")) - # Committed on exit, rolled back on exception -``` - -### Custom Query Execution - -```python -async def fetch_users(): - async with db.session() as session: - result = await session.execute( - select(User).where(User.active == True) - ) - return result.scalars().all() -``` - -### Health Check Pattern - -```python -async def check_database_health(): - health = await db.health_check() - if health["status"] != "healthy": - logger.error(f"Database unhealthy: {health.get('error')}") - # Handle unhealthy state -``` - -## Best Practices - -### Always Use Context Managers - -- ✅ Use `async with db.session()` for all operations -- ✅ Never manually manage session lifecycle -- ✅ Let context manager handle commit/rollback - -### Connection Management - -- ✅ Connect once at startup, reuse service instance -- ✅ Use health checks for validation, not connection testing -- ✅ Let retry logic handle transient failures - -### Error Handling - -- ✅ Let retry logic handle transient errors automatically -- ✅ Log connection errors with context -- ✅ Raise exceptions for non-retryable errors -- ✅ Use Sentry spans for production monitoring - -### Performance Considerations - -- ✅ Reuse DatabaseService instance (singleton pattern) -- ✅ Use connection pooling (automatic with async engine) -- ✅ Enable pre-ping for connection validation -- ✅ Set appropriate pool recycle time - -### Testing Patterns - -- ✅ Mock DatabaseService for unit tests -- ✅ Use real database for integration tests -- ✅ Test retry logic with simulated failures -- ✅ Verify health check behavior - -## Anti-Patterns - -### ❌ Manual Session Management - -```python -# BAD: Manual session management -session = db._session_factory() -try: - # operations - await session.commit() -except: - await session.rollback() -finally: - await session.close() -``` - -### ❌ Direct Engine Access - -```python -# BAD: Bypassing service layer -engine = db.engine -async with engine.begin() as conn: - # Direct connection access -``` - -### ❌ Ignoring Health Checks - -```python -# BAD: No health validation -async def startup(): - db.connect(url) # No validation - # Start accepting requests -``` - -### ❌ Disabling Retry Logic - -```python -# BAD: Catching and ignoring retryable errors -try: - result = await operation() -except DisconnectionError: - # Should let retry logic handle this - pass -``` - -## Related Rules - -- [`overview.mdc`](overview.mdc) - Architecture overview -- [`controllers.mdc`](controllers.mdc) - Controller layer using service -- [`models.mdc`](models.mdc) - Models used with service diff --git a/.cursor/rules/database/services.mdc b/.cursor/rules/database/services.mdc new file mode 100644 index 00000000..bbdc22e5 --- /dev/null +++ b/.cursor/rules/database/services.mdc @@ -0,0 +1,197 @@ +--- +description: DatabaseService patterns, connection management, session handling, and async database operations for Tux +globs: src/tux/database/service.py, **/*database*service*.py +alwaysApply: false +--- + +# Database Service Layer + +## Overview + +Tux uses `DatabaseService` for async PostgreSQL connection management, session handling, and database operations. The service provides connection pooling, retry logic, and health checks. + +## Service Initialization + +### Creating Database Service + +```python +from tux.database.service import DatabaseService + +# ✅ GOOD: Create service instance +db = DatabaseService(echo=False) # echo=True for SQL logging + +# Connect to database +await db.connect(CONFIG.DATABASE_URL) +``` + +❌ **BAD:** Creating multiple service instances or not using service + +```python +# ❌ BAD: Multiple instances +db1 = DatabaseService() +db2 = DatabaseService() # Don't create multiple! +``` + +## Session Management + +### Using Sessions + +```python +# ✅ GOOD: Use session context manager +async with db.session() as session: + # Use session for operations + result = await session.execute(select(MyModel)) + models = result.scalars().all() +``` + +❌ **BAD:** Manual session creation or not using context manager + +```python +# ❌ BAD: Manual session management +session = db._session_factory() +try: + # operations +finally: + await session.close() # Don't do this! +``` + +### Session in Controllers + +Controllers automatically use DatabaseService: + +```python +# ✅ GOOD: Controllers use service internally +controller = MyModelController(db) +model = await controller.get(id=123) # Service manages session + +# ❌ BAD: Passing session to controller +async with db.session() as session: + model = await controller.get(session, id=123) # Don't do this! +``` + +## Connection Management + +### Connection Pooling + +DatabaseService automatically manages connection pooling: + +- **pool_pre_ping**: Tests connections before use +- **pool_recycle**: Recycles connections after 1 hour +- **connect_timeout**: 10-second connection timeout +- **statement_timeout**: 5-minute query timeout + +✅ **GOOD:** Service handles pooling automatically + +❌ **BAD:** Manual connection management + +### Health Checks + +```python +# ✅ GOOD: Use health check method +is_healthy = await db.health_check() + +if not is_healthy: + logger.error("Database connection unhealthy") +``` + +## Async Operations + +### Query Execution + +```python +# ✅ GOOD: Use async session methods +async with db.session() as session: + result = await session.execute( + select(MyModel).where(MyModel.status == "active") + ) + models = result.scalars().all() +``` + +❌ **BAD:** Using sync methods + +```python +# ❌ BAD: Sync operations (not available in async) +async with db.session() as session: + models = session.query(MyModel).all() # Doesn't exist! +``` + +### Transaction Management + +```python +# ✅ GOOD: Use async transaction +async with db.session() as session: + async with session.begin(): + model1 = MyModel(name="Model 1") + model2 = MyModel(name="Model 2") + session.add(model1) + session.add(model2) + # Committed automatically, or rolled back on error +``` + +## Error Handling + +### Connection Errors + +```python +# ✅ GOOD: Handle connection errors +try: + await db.connect(CONFIG.DATABASE_URL) +except Exception as e: + logger.error(f"Failed to connect to database: {e}") + # Handle error appropriately +``` + +### Query Errors + +```python +# ✅ GOOD: Let controllers handle query errors +try: + model = await controller.get(id=123) +except Exception as e: + logger.error(f"Query failed: {e}") + # Handle error +``` + +## Service Lifecycle + +### Startup + +```python +# ✅ GOOD: Connect on startup +async def startup(): + db = DatabaseService() + await db.connect(CONFIG.DATABASE_URL) + return db +``` + +### Shutdown + +```python +# ✅ GOOD: Close connections on shutdown +async def shutdown(db: DatabaseService): + await db.close() +``` + +## Best Practices + +1. **Single service instance** - Create one DatabaseService per application +2. **Use context managers** - Always use `async with db.session()` +3. **Let controllers handle sessions** - Don't pass sessions to controllers +4. **Handle errors properly** - Catch and log database errors +5. **Use health checks** - Monitor database connectivity +6. **Close on shutdown** - Properly close connections + +## Anti-Patterns + +1. ❌ **Multiple service instances** - Use singleton pattern +2. ❌ **Manual session management** - Use context managers +3. ❌ **Passing sessions to controllers** - Controllers manage sessions +4. ❌ **Not handling errors** - Always catch database errors +5. ❌ **Not closing connections** - Close on shutdown + +## See Also + +- @database/controllers.mdc - Controller patterns using service +- @database/models.mdc - Model patterns +- @AGENTS.md - General coding standards +- @rules.mdc - Complete catalog of all rules and commands diff --git a/.cursor/rules/docs/docs.mdc b/.cursor/rules/docs/docs.mdc index 6470fa99..4afc4219 100644 --- a/.cursor/rules/docs/docs.mdc +++ b/.cursor/rules/docs/docs.mdc @@ -201,6 +201,17 @@ This documentation rules system is organized into focused, modular guides: - **Focus on problems users actually encounter** - **Maintain consistency across all documentation** +## See Also + +- @docs/principals.mdc - Documentation frameworks and methodologies +- @docs/style.mdc - Writing standards and formatting +- @docs/syntax.mdc - Zensical syntax and features +- @docs/structure.mdc - Organization and navigation +- @docs/patterns.mdc - Practical examples and templates +- @docs/zensical.mdc - Zensical setup and deployment +- @AGENTS.md - General coding standards +- @rules.mdc - Complete catalog of all rules and commands + --- **Remember**: Documentation is a conversation with users. Make it helpful, clear, and focused on their success with Tux. diff --git a/.cursor/rules/error-handling/logging.mdc b/.cursor/rules/error-handling/logging.mdc new file mode 100644 index 00000000..eba95262 --- /dev/null +++ b/.cursor/rules/error-handling/logging.mdc @@ -0,0 +1,155 @@ +--- +description: Logging patterns, loguru usage, log levels, and structured logging for Tux +globs: src/tux/**/*.py +alwaysApply: false +--- + +# Logging Patterns + +## Overview + +Tux uses loguru for structured logging with automatic formatting, log levels, and context. All logging should use loguru's logger with appropriate log levels. + +## Loguru Usage + +### Basic Logging + +```python +from loguru import logger + +# ✅ GOOD: Use loguru logger +logger.info("Operation completed successfully") +logger.error("Operation failed") +logger.debug("Debug information") +logger.warning("Warning message") +``` + +❌ **BAD:** Using print or standard logging + +```python +# ❌ BAD: Don't use print +print("Operation completed") # Use logger instead! + +# ❌ BAD: Don't use standard logging +import logging +logging.info("Operation completed") # Use loguru! +``` + +### Log Levels + +```python +# ✅ GOOD: Use appropriate log levels +logger.trace("Very detailed debugging") +logger.debug("Debug information") +logger.info("General information") +logger.success("Successful operation") +logger.warning("Warning message") +logger.error("Error occurred") +logger.critical("Critical error") +``` + +✅ **GOOD:** Appropriate log level for message importance + +## Structured Logging + +### Context in Logs + +```python +# ✅ GOOD: Include context in logs +logger.info(f"Processing guild {guild_id} with {member_count} members") +logger.error(f"Failed to process command {command_name} for user {user_id}") +``` + +### Exception Logging + +```python +# ✅ GOOD: Log exceptions with context +try: + await operation() +except Exception as e: + logger.exception("Operation failed") # Includes traceback + # Or with custom message + logger.opt(exception=True).error(f"Operation failed: {e}") +``` + +❌ **BAD:** Logging without exception info + +```python +# ❌ BAD: Missing exception info +try: + await operation() +except Exception as e: + logger.error(f"Operation failed: {e}") # Missing traceback! +``` + +## Log Configuration + +### Log Levels + +Log levels are configured in `CONFIG.LOG_LEVEL`: + +```python +# ✅ GOOD: Use configured log level +# Logging is configured in core/logging.py +# Respects CONFIG.LOG_LEVEL setting +``` + +### Testing Logging + +```python +# ✅ GOOD: Configure test logging +from tux.core.logging import configure_testing_logging + +configure_testing_logging() +``` + +## Log Formatting + +### Automatic Formatting + +Loguru automatically formats logs with: + +- Timestamp +- Log level +- Module name +- Line number +- Message + +✅ **GOOD:** Automatic formatting provides context + +### Custom Formatting + +```python +# ✅ GOOD: Custom formatting when needed +logger.add( + sink, + format="{time} | {level} | {name}:{function}:{line} | {message}", +) +``` + +## Best Practices + +1. **Use loguru** - Always use loguru logger +2. **Appropriate levels** - Use correct log level +3. **Include context** - Add relevant context to logs +4. **Log exceptions** - Use logger.exception() or logger.opt(exception=True) +5. **Structured logs** - Include relevant data in logs +6. **Don't log secrets** - Sanitize sensitive data +7. **Respect log levels** - Don't log debug in production + +## Anti-Patterns + +1. ❌ **Using print** - Always use logger +2. ❌ **Wrong log level** - Use appropriate level +3. ❌ **Missing context** - Include relevant context +4. ❌ **No exception info** - Use exception logging +5. ❌ **Logging secrets** - Sanitize sensitive data +6. ❌ **Too verbose** - Don't log everything at debug + +## See Also + +- @error-handling/patterns.mdc - Error handling patterns +- @error-handling/sentry.mdc - Sentry integration +- @security/secrets.mdc - Secret sanitization +- @AGENTS.md - General coding standards +- @rules.mdc - Complete catalog of all rules and commands diff --git a/.cursor/rules/error-handling/patterns.mdc b/.cursor/rules/error-handling/patterns.mdc new file mode 100644 index 00000000..c06ad9cc --- /dev/null +++ b/.cursor/rules/error-handling/patterns.mdc @@ -0,0 +1,163 @@ +--- +description: Error handling patterns, exception handling, error types, and error handling best practices for Tux +globs: src/tux/**/*.py +alwaysApply: false +--- + +# Error Handling Patterns + +## Overview + +Tux uses a centralized error handling system with custom exceptions, proper error logging, and user-friendly error messages. All errors should be handled appropriately with proper context. + +## Exception Hierarchy + +### Custom Exceptions + +Tux defines custom exceptions in `tux.shared.exceptions`: + +```python +from tux.shared.exceptions import TuxError, TuxPermissionDeniedError + +# ✅ GOOD: Use custom exceptions +if not has_permission: + raise TuxPermissionDeniedError("User lacks required permission") +``` + +✅ **GOOD:** Custom exceptions provide context + +### Base Exception + +```python +class TuxError(Exception): + """Base exception for all Tux-specific errors.""" + pass +``` + +All Tux exceptions inherit from `TuxError`. + +## Error Handling Patterns + +### Try-Except Blocks + +```python +# ✅ GOOD: Handle errors with try-except +try: + result = await operation() +except ValueError as e: + logger.error(f"Validation error: {e}") + await ctx.send("Invalid input provided.") +except Exception as e: + logger.exception("Unexpected error") + await ctx.send("An error occurred.") +``` + +❌ **BAD:** Swallowing errors or not handling them + +```python +# ❌ BAD: Swallowing errors +try: + await operation() +except Exception: + pass # Silent failure! + +# ❌ BAD: Not handling errors +result = await operation() # No error handling! +``` + +### Error Context + +```python +# ✅ GOOD: Provide context in errors +try: + await database_operation() +except DatabaseError as e: + logger.error(f"Database operation failed: {e}", exc_info=True) + raise TuxDatabaseError("Failed to access database") from e +``` + +✅ **GOOD:** Preserve original exception with `from e` + +## Command Error Handling + +### Local Error Handlers + +```python +# ✅ GOOD: Local error handler for specific errors +@commands.hybrid_command(name="command") +async def command(self, ctx: commands.Context[Tux]) -> None: + """Command description.""" + pass + +@command.error +async def command_error( + self, + ctx: commands.Context[Tux], + error: commands.CommandError, +) -> None: + """Handle command-specific errors.""" + if isinstance(error, commands.MissingRequiredArgument): + await ctx.send("Missing required argument.") + else: + # Let global handler handle other errors + raise +``` + +### Global Error Handler + +The global error handler in `ErrorHandler` cog handles all unhandled errors: + +```python +# ✅ GOOD: Global handler catches all unhandled errors +# ErrorHandler cog automatically handles errors +``` + +## Error Propagation + +### Re-raising Exceptions + +```python +# ✅ GOOD: Re-raise with context +try: + await operation() +except ValueError as e: + logger.error(f"Operation failed: {e}") + raise TuxError("Operation failed") from e +``` + +### Error Wrapping + +```python +# ✅ GOOD: Wrap exceptions with context +try: + await database_operation() +except DatabaseError as e: + raise TuxDatabaseError("Database operation failed") from e +``` + +## Best Practices + +1. **Use custom exceptions** - Use Tux-specific exceptions +2. **Handle errors appropriately** - Don't swallow errors +3. **Provide context** - Include context in error messages +4. **Log errors** - Always log errors with context +5. **Preserve exceptions** - Use `from e` when re-raising +6. **User-friendly messages** - Provide clear error messages to users +7. **Let global handler work** - Don't handle everything locally + +## Anti-Patterns + +1. ❌ **Swallowing errors** - Always handle or log errors +2. ❌ **Generic exceptions** - Use specific exception types +3. ❌ **No context** - Include context in error messages +4. ❌ **Silent failures** - Log all errors +5. ❌ **Exposing internals** - Use generic messages for users +6. ❌ **Not preserving exceptions** - Use `from e` when re-raising + +## See Also + +- @error-handling/logging.mdc - Logging patterns +- @error-handling/sentry.mdc - Sentry integration +- @error-handling/user-feedback.mdc - User error messages +- @AGENTS.md - General coding standards +- @rules.mdc - Complete catalog of all rules and commands diff --git a/.cursor/rules/error-handling/sentry.mdc b/.cursor/rules/error-handling/sentry.mdc new file mode 100644 index 00000000..2aa9039f --- /dev/null +++ b/.cursor/rules/error-handling/sentry.mdc @@ -0,0 +1,132 @@ +--- +description: Sentry integration, error tracking, performance monitoring, and Sentry best practices for Tux +globs: src/tux/services/sentry/**/*.py, src/tux/**/*.py +alwaysApply: false +--- + +# Sentry Integration + +## Overview + +Tux uses Sentry for error tracking and performance monitoring. Errors are automatically captured with context, and sensitive data is sanitized before sending. + +## Error Capture + +### Automatic Capture + +```python +from tux.services.sentry import capture_exception_safe + +# ✅ GOOD: Capture exceptions safely +try: + await operation() +except Exception as e: + capture_exception_safe(e) + # Error sent to Sentry with context +``` + +✅ **GOOD:** Automatic context capture + +### Manual Capture + +```python +# ✅ GOOD: Manual capture with extra context +capture_exception_safe( + error, + extra_context={"guild_id": guild_id, "user_id": user_id}, + capture_locals=True, +) +``` + +## Context Setting + +### Command Context + +```python +from tux.services.sentry import set_command_context + +# ✅ GOOD: Set command context +set_command_context(ctx) # Includes command, user, guild info +``` + +### User Context + +```python +from tux.services.sentry import set_user_context + +# ✅ GOOD: Set user context +set_user_context(user) # Includes user ID, username, roles +``` + +### Combined Context + +```python +# ✅ GOOD: Set both contexts +set_command_context(ctx) +set_user_context(ctx.author) +``` + +## Error Handler Integration + +The error handler automatically sets Sentry context: + +```python +# ✅ GOOD: Error handler sets context automatically +# ErrorHandler cog sets context before capturing errors +``` + +## Data Sanitization + +### Automatic Sanitization + +Sentry automatically sanitizes sensitive data: + +```python +from tux.services.sentry.handlers import _sanitize_sensitive_data + +# ✅ GOOD: Sensitive data is sanitized +# Database URLs, API keys, tokens are masked +``` + +✅ **GOOD:** Sensitive data never sent to Sentry + +## Performance Monitoring + +### Command Tracking + +```python +from tux.services.sentry import track_command_start, track_command_end + +# ✅ GOOD: Track command performance +track_command_start(command_name) +try: + await execute_command() + track_command_end(command_name, success=True) +except Exception as e: + track_command_end(command_name, success=False, error=e) +``` + +## Best Practices + +1. **Use capture_exception_safe** - Safe error capture +2. **Set context** - Always set command and user context +3. **Sanitize data** - Sensitive data is automatically sanitized +4. **Track performance** - Use performance tracking for commands +5. **Don't capture manually** - Let error handler capture automatically +6. **Extra context** - Add extra context when needed + +## Anti-Patterns + +1. ❌ **Direct sentry_sdk.capture_exception** - Use capture_exception_safe +2. ❌ **Missing context** - Always set context +3. ❌ **Logging secrets** - Sanitization handles this +4. ❌ **Manual capture everywhere** - Let error handler work +5. ❌ **No performance tracking** - Track important operations + +## See Also + +- @error-handling/patterns.mdc - Error handling patterns +- @error-handling/logging.mdc - Logging patterns +- @security/secrets.mdc - Secret sanitization +- @AGENTS.md - General coding standards +- @rules.mdc - Complete catalog of all rules and commands diff --git a/.cursor/rules/error-handling/user-feedback.mdc b/.cursor/rules/error-handling/user-feedback.mdc new file mode 100644 index 00000000..6d3e7c4d --- /dev/null +++ b/.cursor/rules/error-handling/user-feedback.mdc @@ -0,0 +1,146 @@ +--- +description: User-facing error messages, error embeds, user-friendly error responses, and error communication for Tux +globs: src/tux/**/*.py +alwaysApply: false +--- + +# User-Facing Error Messages + +## Overview + +Tux provides user-friendly error messages through embeds and formatted responses. Error messages should be clear, helpful, and not expose internal details. + +## Error Embeds + +### Using ErrorFormatter + +```python +from tux.services.handlers.error.formatter import ErrorFormatter + +# ✅ GOOD: Use ErrorFormatter for consistent error messages +formatter = ErrorFormatter() +embed = formatter.format_error_embed(error, ctx, config) +await ctx.send(embed=embed) +``` + +✅ **GOOD:** Consistent error formatting + +### Manual Error Embeds + +```python +from tux.ui.embeds import EmbedCreator + +# ✅ GOOD: Use EmbedCreator for error embeds +embed = EmbedCreator.error( + title="Error Title", + description="User-friendly error message" +) +await ctx.send(embed=embed) +``` + +❌ **BAD:** Exposing internal errors + +```python +# ❌ BAD: Exposing internal error details +try: + await operation() +except Exception as e: + await ctx.send(f"Error: {e}") # Exposes internal details! +``` + +## Error Message Patterns + +### Permission Errors + +```python +# ✅ GOOD: Clear permission error message +embed = EmbedCreator.error( + title="Permission Denied", + description="You don't have permission to use this command." +) +await ctx.send(embed=embed) +``` + +### Validation Errors + +```python +# ✅ GOOD: Clear validation error message +embed = EmbedCreator.error( + title="Invalid Input", + description="Reason must be 500 characters or less." +) +await ctx.send(embed=embed) +``` + +### Generic Errors + +```python +# ✅ GOOD: Generic error for unexpected errors +embed = EmbedCreator.error( + title="Error", + description="An unexpected error occurred. Please try again later." +) +await ctx.send(embed=embed) +``` + +## Error Handler Messages + +The error handler automatically formats error messages: + +```python +# ✅ GOOD: Error handler formats messages automatically +# ErrorHandler cog formats errors based on error type +``` + +## Interaction Error Responses + +### Ephemeral Responses + +```python +# ✅ GOOD: Use ephemeral for error responses +await interaction.response.send_message( + embed=error_embed, + ephemeral=True # Only visible to user +) +``` + +### Follow-up Responses + +```python +# ✅ GOOD: Use follow-up if response already sent +if interaction.response.is_done(): + await interaction.followup.send( + embed=error_embed, + ephemeral=True + ) +else: + await interaction.response.send_message( + embed=error_embed, + ephemeral=True + ) +``` + +## Best Practices + +1. **User-friendly messages** - Clear, helpful error messages +2. **Use embeds** - Use error embeds for consistency +3. **Don't expose internals** - Generic messages for unexpected errors +4. **Be specific** - Specific messages for validation/permission errors +5. **Ephemeral responses** - Use ephemeral for error responses +6. **Consistent formatting** - Use ErrorFormatter or EmbedCreator + +## Anti-Patterns + +1. ❌ **Exposing internals** - Don't show internal error details +2. ❌ **Generic messages** - Be specific when possible +3. ❌ **Plain text** - Use embeds for errors +4. ❌ **Not ephemeral** - Use ephemeral for error responses +5. ❌ **Inconsistent formatting** - Use standard formatters + +## See Also + +- @error-handling/patterns.mdc - Error handling patterns +- @ui/embeds.mdc - Embed patterns (if exists) +- @modules/interactions.mdc - Interaction patterns +- @AGENTS.md - General coding standards +- @rules.mdc - Complete catalog of all rules and commands diff --git a/.cursor/rules/meta/cursor-commands.mdc b/.cursor/rules/meta/cursor-commands.mdc new file mode 100644 index 00000000..00504067 --- /dev/null +++ b/.cursor/rules/meta/cursor-commands.mdc @@ -0,0 +1,203 @@ +--- +globs: *.mdc,AGENTS.md +alwaysApply: false +--- +# Cursor Commands Specification + +## Command Type Classification + +| Type | Location | Availability | Management | +|------|----------|-------------|------------| +| Project | `.cursor/commands/` | Current project only | Version controlled | +| Global | `~/.cursor/commands/` | All projects | User-level | +| Team | Dashboard managed | All team members | Admin-controlled | + +## File Format Requirements + +Commands MUST: + +- Use plain Markdown (`.md`) format +- Have descriptive kebab-case filenames +- Contain clear, actionable content +- Be stored in `.cursor/commands/` directory + +Commands MUST NOT: + +- Use MDC format or YAML frontmatter +- Exceed reasonable length (recommend 200 lines) +- Duplicate functionality of existing commands +- Include sensitive information or secrets + +## Invocation + +**Syntax**: `/command-name [optional parameters]` + +**Behavior**: + +- Triggered with `/` prefix in chat input +- Filename (without `.md`) becomes command name +- Auto-discovered and displayed on `/` input +- Parameters passed as additional context + +**Example**: + +```text +/review-code and check for security issues +``` + +Command: `review-code` +Parameters: `and check for security issues` + +## Command Structure + +```markdown +# Title + +## Overview +Brief description of command purpose. + +## Steps +1. Action one +2. Action two +3. Action three + +## Checklist +- [ ] Item one +- [ ] Item two +``` + +## Content Requirements + +Commands SHOULD: + +- Start with clear title and overview +- Use step-by-step instructions for workflows +- Include checklists for verification +- Be specific to project workflows +- Focus on repeatability + +Commands MAY: + +- Reference project-specific tools +- Include code block templates +- Define acceptance criteria +- Link to related documentation + +## Naming Conventions + +Filenames MUST: + +- Use kebab-case: `command-name.md` +- Be descriptive and action-oriented +- Avoid generic names +- Match expected usage pattern + +**Good**: + +- `review-code.md` +- `create-pr.md` +- `run-tests-and-fix.md` +- `security-audit.md` + +**Bad**: + +- `command.md` +- `helper.md` +- `tool.md` +- `misc.md` + +## Directory Organization + +```text +.cursor/commands/ +├── code-review.md +├── create-pr.md +├── run-tests.md +└── security-audit.md +``` + +No nested directories supported. All commands in flat structure. + +## Parameter Handling + +Parameters are appended to command content in model context: + +- Optional: Commands work with or without parameters +- Freeform: Any text after command name +- Context: Parameters add specificity to command execution + +## Team Commands + +**Requirements**: + +- Created via [Cursor Dashboard](https://cursor.com/dashboard?tab=team-content§ion=commands) +- Available only on Team/Enterprise plans +- Automatically synchronized to all team members +- Admin-only creation and modification + +**Fields**: + +- **Name** (required): Command identifier +- **Description** (optional): Help text for users +- **Content** (required): Markdown command content + +**Benefits**: + +- Centralized management +- Instant synchronization +- Standardized workflows +- Access control + +## Validation Criteria + +Commands MUST: + +- Be valid Markdown +- Have unique names within scope +- Contain actionable content +- Be project/workflow specific + +Commands SHOULD: + +- Be tested before committing +- Have clear purpose +- Be updated regularly +- Be documented if complex + +## Best Practices + +**Scope**: + +- One workflow per command +- Clear, focused purpose +- Avoid command overlap + +**Content**: + +- Action-oriented language +- Specific instructions +- Project context included +- Repeatable process + +**Maintenance**: + +- Remove unused commands +- Update with workflow changes +- Validate functionality periodically +- Version control project commands + +## Command vs Rule + +| Aspect | Commands | Rules | +|--------|----------|-------| +| Format | Plain Markdown | MDC with metadata | +| Trigger | Manual `/command` | Automatic/intelligent | +| Purpose | Workflows/actions | Guidelines/standards | +| Scope | User-initiated | Context-applied | +| Location | `.cursor/commands/` | `.cursor/rules/` | + +## File References + +- Project commands: `.cursor/commands/*.md` +- Global commands: `~/.cursor/commands/*.md` +- Team commands: Dashboard managed diff --git a/.cursor/rules/meta/cursor-rules.mdc b/.cursor/rules/meta/cursor-rules.mdc new file mode 100644 index 00000000..2dd58c00 --- /dev/null +++ b/.cursor/rules/meta/cursor-rules.mdc @@ -0,0 +1,160 @@ +--- +globs: *.mdc,AGENTS.md +alwaysApply: false +--- +# Cursor Rules Specification + +## Rule Type Classification + +| Type | Metadata | Application | +|------|----------|-------------| +| Always Apply | `alwaysApply: true` | Every chat session, project-wide standards | +| Intelligent | `alwaysApply: false` + `description` | AI-selected based on relevance | +| File-Scoped | `globs: pattern1, pattern2` | Matches file patterns (`*.py`, `src/api/**`) | +| Manual | No globs, `alwaysApply: false` | Invoked via @-mention only | + +## MDC Format Requirements + +```markdown +--- +description: Required for intelligent application, max 120 chars +globs: optional/file/pattern, another/pattern # Comma-separated glob patterns +alwaysApply: false # Boolean, default false +--- + +# Title (Required) + +## Content Structure +- Use markdown headers for organization +- Bullet points for scannable directives +- Code blocks for examples +- @-reference files for context + +## Patterns +Declarative statements and requirements. + +## Anti-Patterns +- ❌ BAD: Counter-example +- ✅ GOOD: Correct pattern +``` + +## Constraints + +- **Size**: MAX 500 lines per rule file +- **Scope**: One domain/concern per rule +- **Specificity**: Project-specific only, no general coding advice +- **Naming**: Descriptive kebab-case filenames (`database-models.mdc`) +- **Location**: `.cursor/rules/` in project root or subdirectories + +## Content Requirements + +MUST include: + +- Clear title describing rule scope +- Concrete, actionable directives +- Project-specific patterns + +MUST NOT include: + +- Generic programming advice +- Redundant LLM knowledge +- Vague or abstract guidance +- Conflicting directives with other rules + +SHOULD include: + +- Code examples demonstrating patterns +- File references with `@filename.ext` +- Anti-patterns with ❌ ✅ markers +- Relationship links to related rules + +MAY include: + +- Templates for code generation +- Workflow step sequences +- Decision trees or conditionals + +## Directory Organization + +```text +.cursor/rules/ +├── *.mdc # Project-wide rules +└── subdirectory/ + └── .cursor/rules/ + └── *.mdc # Directory-scoped rules +``` + +Nested rules inherit parent rules and auto-apply within their directory scope. + +## Rule Type Selection Matrix + +| Scenario | Type | Config | +|----------|------|--------| +| Universal coding standards | Always Apply | `alwaysApply: true` | +| Database layer patterns | Intelligent | `description` + domain keywords | +| Test file conventions | File-Scoped | `globs: tests/**/*.py` | +| Code generation template | Manual | No metadata | +| Framework-specific patterns | Intelligent | `description` referencing framework | + +## Metadata Specification + +### `description` Field + +- Required for intelligent application +- 60-120 characters +- Include domain keywords for matching +- Clear indication of rule scope + +### `globs` Field + +- Array of glob pattern strings +- Patterns relative to project root +- Supports: `*`, `**`, `?`, `[abc]`, `{a,b}` +- Examples: `["src/**/*.py", "tests/unit/**"]` + +### `alwaysApply` Field + +- Boolean: `true` | `false` +- Default: `false` +- Use sparingly to prevent context bloat +- Reserved for universal project standards + +## Validation Criteria + +Rule files MUST: + +- Use `.mdc` extension +- Include valid YAML frontmatter +- Define clear title +- Contain actionable content +- Be under 500 lines + +Rule content MUST: + +- Be project-specific +- Provide concrete examples +- Avoid conflicts with existing rules +- Reference actual project files + +Rule metadata MUST: + +- Use valid glob patterns if defined +- Include description for intelligent rules +- Set appropriate application type + +## Maintenance Requirements + +- Test rules via @-mention before enabling +- Update rules when project patterns evolve +- Remove rules that don't improve AI output +- Split rules exceeding 500 lines +- Merge redundant rules +- Validate glob patterns match intended files +- Check for conflicts with other rules + +## File References + +- Project rules: `.cursor/rules/*.mdc` +- Nested rules: `path/to/.cursor/rules/*.mdc` +- Legacy (deprecated): `.cursorrules` +- Alternative: `AGENTS.md` diff --git a/.cursor/rules/modules/cogs.mdc b/.cursor/rules/modules/cogs.mdc new file mode 100644 index 00000000..8945f923 --- /dev/null +++ b/.cursor/rules/modules/cogs.mdc @@ -0,0 +1,224 @@ +--- +description: Discord bot cog structure, BaseCog usage, cog organization, and module patterns for Tux +globs: src/tux/modules/**/*.py +alwaysApply: false +--- + +# Discord Bot Cogs + +## Overview + +Tux uses discord.py cogs organized by feature domain. All cogs inherit from `BaseCog` which provides database access, configuration, and automatic usage generation. + +## Cog Structure + +### Basic Cog Pattern + +```python +from discord.ext import commands +from tux.core.base_cog import BaseCog +from tux.core.bot import Tux + +class MyCog(BaseCog): + """Description of what this cog does.""" + + def __init__(self, bot: Tux) -> None: + """Initialize the cog. + + Parameters + ---------- + bot : Tux + The bot instance. + """ + super().__init__(bot) + # Initialize any services or state here +``` + +✅ **GOOD:** Inherits from BaseCog, proper typing, NumPy docstrings + +❌ **BAD:** Direct commands.Cog inheritance, missing BaseCog + +```python +# ❌ BAD: Missing BaseCog benefits +class MyCog(commands.Cog): + def __init__(self, bot: Tux): + self.bot = bot # Missing database access, config, etc. +``` + +## Cog Organization + +### Directory Structure + +Cogs are organized by feature domain: + +``` +src/tux/modules/ +├── admin/ # Administrative commands +├── config/ # Configuration management +├── features/ # Feature modules (levels, starboard, etc.) +├── fun/ # Fun/entertainment commands +├── guild/ # Guild-specific features +├── info/ # Information commands +├── levels/ # Leveling system +├── moderation/ # Moderation commands +├── snippets/ # Snippet management +├── tools/ # Utility tools +└── utility/ # Utility commands +``` + +✅ **GOOD:** Domain-based organization, clear separation + +❌ **BAD:** Flat structure, unclear organization + +### Module Files + +Each module directory contains: + +- `__init__.py` - Module exports and base classes +- Individual cog files - One cog per file +- `README.md` (optional) - Module documentation + +✅ **GOOD:** One cog per file, clear module structure + +## BaseCog Features + +### Database Access + +BaseCog provides `self.db` for database operations: + +```python +# ✅ GOOD: Use self.db for database access +class MyCog(BaseCog): + async def my_command(self, ctx: commands.Context[Tux]) -> None: + guild_controller = self.db.guild + guild = await guild_controller.get(id=ctx.guild.id) +``` + +❌ **BAD:** Direct database service access + +```python +# ❌ BAD: Don't access database service directly +async def my_command(self, ctx: commands.Context[Tux]) -> None: + db = DatabaseService() # Don't create new instances! +``` + +### Configuration Access + +BaseCog provides `self.config` for guild configuration: + +```python +# ✅ GOOD: Use self.config for guild config +guild_config = await self.config.get_guild_config(ctx.guild.id) +``` + +### Automatic Usage Generation + +BaseCog automatically generates usage strings from function signatures: + +```python +# ✅ GOOD: Usage generated automatically from signature +@commands.hybrid_command(name="mycommand") +async def my_command( + self, + ctx: commands.Context[Tux], + member: discord.Member, + reason: str | None = None, +) -> None: + # Usage: mycommand [reason: str] + pass +``` + +## Cog Lifecycle + +### Initialization + +```python +def __init__(self, bot: Tux) -> None: + super().__init__(bot) + # Initialize services, state, etc. +``` + +### Loading + +```python +async def cog_load(self) -> None: + """Called when cog is loaded.""" + # Setup tasks, initialize caches, etc. + pass +``` + +### Unloading + +```python +async def cog_unload(self) -> None: + """Called when cog is unloaded.""" + # Cleanup tasks, close connections, etc. + pass +``` + +## Cog Setup Function + +Every cog must have a setup function: + +```python +async def setup(bot: Tux) -> None: + """Set up the cog. + + Parameters + ---------- + bot : Tux + The bot instance to add the cog to. + """ + await bot.add_cog(MyCog(bot)) +``` + +✅ **GOOD:** Proper setup function with type hints + +❌ **BAD:** Missing setup function or wrong signature + +## Specialized Base Classes + +### ModerationCogBase + +For moderation cogs: + +```python +from tux.modules.moderation import ModerationCogBase + +class Warn(ModerationCogBase): + """Warning moderation cog.""" + + def __init__(self, bot: Tux) -> None: + super().__init__(bot) + # Moderation services automatically available + # self.moderation - ModerationCoordinator +``` + +✅ **GOOD:** Use specialized base classes when available + +## Best Practices + +1. **Always inherit from BaseCog** - Provides database, config, usage generation +2. **Organize by domain** - Group related cogs in directories +3. **One cog per file** - Keep cogs focused and maintainable +4. **Use setup function** - Required for cog loading +5. **Initialize in **init**** - Set up services and state +6. **Handle lifecycle** - Implement cog_load/cog_unload if needed +7. **Type hints** - Always type bot parameter and return types + +## Anti-Patterns + +1. ❌ **Direct commands.Cog** - Always use BaseCog +2. ❌ **Multiple cogs per file** - One cog per file +3. ❌ **Missing setup function** - Required for loading +4. ❌ **Direct database access** - Use self.db +5. ❌ **Flat organization** - Organize by domain +6. ❌ **No type hints** - Always type bot and methods + +## See Also + +- @modules/commands.mdc - Command patterns +- @modules/events.mdc - Event handler patterns +- @modules/permissions.mdc - Permission patterns +- @AGENTS.md - General coding standards +- @rules.mdc - Complete catalog of all rules and commands diff --git a/.cursor/rules/modules/commands.mdc b/.cursor/rules/modules/commands.mdc new file mode 100644 index 00000000..87b53e78 --- /dev/null +++ b/.cursor/rules/modules/commands.mdc @@ -0,0 +1,319 @@ +--- +description: Discord command patterns, hybrid commands, slash commands, command decorators, and response patterns for Tux +globs: src/tux/modules/**/*.py +alwaysApply: false +--- + +# Discord Bot Commands + +## Overview + +Tux uses discord.py hybrid commands (slash + traditional) with proper decorators, error handling, and permission checks. Commands should be well-typed, documented, and follow Tux patterns. + +## Command Types + +### Hybrid Commands + +Hybrid commands work as both slash and traditional commands: + +```python +@commands.hybrid_command( + name="mycommand", + aliases=["mc", "cmd"], + description="Command description for slash command" +) +@commands.guild_only() +@requires_command_permission() +async def my_command( + self, + ctx: commands.Context[Tux], + member: discord.Member, + reason: str | None = None, +) -> None: + """Command description (appears in help). + + Parameters + ---------- + ctx : commands.Context[Tux] + The command context. + member : discord.Member + The member to act on. + reason : str, optional + Optional reason for the action. + """ + assert ctx.guild # Required for guild_only commands + # Command implementation +``` + +✅ **GOOD:** Hybrid command, proper decorators, type hints, docstring + +❌ **BAD:** Missing decorators, no type hints + +```python +# ❌ BAD: Missing decorators and type hints +@commands.command() +async def my_command(self, ctx, member): + # Missing guild_only, permissions, type hints + pass +``` + +### Traditional Commands + +For commands that don't need slash support: + +```python +@commands.command( + name="mycommand", + aliases=["mc"], +) +@commands.guild_only() +@requires_command_permission() +async def my_command( + self, + ctx: commands.Context[Tux], + member: discord.Member, +) -> None: + """Command description.""" + # Implementation +``` + +### Command Groups + +For nested commands: + +```python +@commands.hybrid_group( + name="config", + aliases=["settings"], +) +@commands.guild_only() +@requires_command_permission() +async def config(self, ctx: commands.Context[Tux]) -> None: + """Manage guild configuration.""" + if ctx.invoked_subcommand is None: + # Show help or default action + await self.show_config_overview(ctx) + +@config.command(name="set") +@commands.guild_only() +@requires_command_permission() +async def config_set( + self, + ctx: commands.Context[Tux], + key: str, + value: str, +) -> None: + """Set a configuration value.""" + # Implementation +``` + +✅ **GOOD:** Proper group structure, subcommand handling + +## Command Decorators + +### Required Decorators + +```python +# ✅ GOOD: Always include these decorators +@commands.hybrid_command(name="command") +@commands.guild_only() # If command requires guild +@requires_command_permission() # Permission check +async def command(self, ctx: commands.Context[Tux]) -> None: + pass +``` + +### Guild-Only Commands + +```python +# ✅ GOOD: Use guild_only for commands that need a guild +@commands.guild_only() +async def command(self, ctx: commands.Context[Tux]) -> None: + assert ctx.guild # Type narrowing after guild_only + # Safe to use ctx.guild +``` + +### Permission Checks + +```python +# ✅ GOOD: Use permission decorator +@requires_command_permission() +async def command(self, ctx: commands.Context[Tux]) -> None: + # Permission checked automatically + pass +``` + +❌ **BAD:** Manual permission checks + +```python +# ❌ BAD: Don't manually check permissions +async def command(self, ctx: commands.Context[Tux]) -> None: + if not await check_permission(ctx): + return # Use decorator instead! +``` + +## Command Parameters + +### Type Hints + +Always use proper type hints: + +```python +# ✅ GOOD: Proper type hints +async def command( + self, + ctx: commands.Context[Tux], + member: discord.Member, + reason: str | None = None, + count: int = 1, +) -> None: + pass +``` + +❌ **BAD:** Missing or incorrect type hints + +```python +# ❌ BAD: Missing type hints +async def command(self, ctx, member, reason=None): + pass +``` + +### Optional Parameters + +```python +# ✅ GOOD: Use | None for optional parameters +async def command( + self, + ctx: commands.Context[Tux], + member: discord.Member, + reason: str | None = None, # Optional +) -> None: + if reason: + # Handle reason + pass +``` + +### Flag Parameters + +For complex parameters, use Flag classes: + +```python +from tux.core.flags import WarnFlags + +@commands.hybrid_command(name="warn") +async def warn( + self, + ctx: commands.Context[Tux], + member: discord.Member, + *, + flags: WarnFlags, # Flags for complex options +) -> None: + reason = flags.reason + silent = flags.silent +``` + +## Command Responses + +### Sending Messages + +```python +# ✅ GOOD: Use appropriate response method +await ctx.send("Simple message") +await ctx.send(embed=embed) # With embed +await ctx.send(view=view) # With view/components +``` + +### Deferring Responses + +For long-running operations: + +```python +# ✅ GOOD: Defer for long operations +if ctx.interaction: + await ctx.interaction.response.defer() + +# Do long operation +result = await long_operation() + +# Edit or follow up +if ctx.interaction: + await ctx.interaction.followup.send(result) +else: + await ctx.send(result) +``` + +### Error Responses + +```python +# ✅ GOOD: Use error embeds +from tux.ui.embeds import EmbedCreator + +embed = EmbedCreator.error( + title="Error", + description="Something went wrong" +) +await ctx.send(embed=embed) +``` + +## Command Error Handling + +### Try-Except Blocks + +```python +# ✅ GOOD: Handle errors properly +async def command(self, ctx: commands.Context[Tux]) -> None: + try: + # Command logic + pass + except discord.Forbidden: + embed = EmbedCreator.error( + title="Permission Denied", + description="I don't have permission to do that." + ) + await ctx.send(embed=embed) + except Exception as e: + logger.exception("Command failed") + await ctx.send("An error occurred.") +``` + +### Error Handler Cogs + +For global error handling: + +```python +@commands.Cog.listener() +async def on_command_error( + self, + ctx: commands.Context[Tux], + error: commands.CommandError, +) -> None: + # Handle command errors + pass +``` + +## Best Practices + +1. **Use hybrid commands** - Support both slash and traditional +2. **Always type hints** - Type all parameters and return types +3. **Use decorators** - guild_only, requires_command_permission +4. **Document commands** - NumPy docstrings +5. **Handle errors** - Try-except with appropriate responses +6. **Use embeds** - For rich responses +7. **Assert ctx.guild** - After guild_only decorator + +## Anti-Patterns + +1. ❌ **Missing type hints** - Always type parameters +2. ❌ **Missing decorators** - Use required decorators +3. ❌ **No error handling** - Always handle errors +4. ❌ **Plain text responses** - Use embeds for rich responses +5. ❌ **Manual permission checks** - Use decorators +6. ❌ **No docstrings** - Document all commands + +## See Also + +- @modules/cogs.mdc - Cog structure +- @modules/permissions.mdc - Permission patterns +- @modules/interactions.mdc - Interaction patterns +- @ui/cv2.mdc - Components V2 patterns +- @AGENTS.md - General coding standards +- @rules.mdc - Complete catalog of all rules and commands diff --git a/.cursor/rules/modules/events.mdc b/.cursor/rules/modules/events.mdc new file mode 100644 index 00000000..167abe38 --- /dev/null +++ b/.cursor/rules/modules/events.mdc @@ -0,0 +1,265 @@ +--- +description: Discord event handler patterns, Cog listeners, event organization, and event handling best practices for Tux +globs: src/tux/**/*.py +alwaysApply: false +--- + +# Discord Bot Event Handlers + +## Overview + +Tux uses discord.py event listeners organized in cogs. Event handlers should be properly typed, handle errors, and follow Tux patterns for logging and error tracking. + +## Event Listener Pattern + +### Basic Event Listener + +```python +@commands.Cog.listener() +async def on_message(self, message: discord.Message) -> None: + """Handle message events. + + Parameters + ---------- + message : discord.Message + The message that was sent. + """ + # Ignore bot messages + if message.author.bot: + return + + # Event handling logic + pass +``` + +✅ **GOOD:** Proper decorator, type hints, docstring, bot check + +❌ **BAD:** Missing decorator, no type hints + +```python +# ❌ BAD: Missing @commands.Cog.listener() +async def on_message(self, message): + pass +``` + +### Named Event Listeners + +For specific events: + +```python +@commands.Cog.listener("on_member_join") +async def on_member_join(self, member: discord.Member) -> None: + """Handle member join events.""" + # Welcome logic + pass + +@commands.Cog.listener("on_command_error") +async def on_command_error( + self, + ctx: commands.Context[Tux], + error: commands.CommandError, +) -> None: + """Handle command errors.""" + # Error handling + pass +``` + +✅ **GOOD:** Named listener, proper error types + +## Common Events + +### Message Events + +```python +@commands.Cog.listener() +async def on_message(self, message: discord.Message) -> None: + """Handle all messages.""" + if message.author.bot: + return + # Process message + pass + +@commands.Cog.listener() +async def on_message_edit( + self, + before: discord.Message, + after: discord.Message, +) -> None: + """Handle message edits.""" + # Handle edit + pass + +@commands.Cog.listener() +async def on_message_delete(self, message: discord.Message) -> None: + """Handle message deletions.""" + # Handle deletion + pass +``` + +### Member Events + +```python +@commands.Cog.listener() +async def on_member_join(self, member: discord.Member) -> None: + """Handle member joins.""" + assert member.guild + # Welcome member, assign roles, etc. + pass + +@commands.Cog.listener() +async def on_member_remove(self, member: discord.Member) -> None: + """Handle member leaves.""" + # Cleanup, logging, etc. + pass + +@commands.Cog.listener() +async def on_member_update( + self, + before: discord.Member, + after: discord.Member, +) -> None: + """Handle member updates (roles, nickname, etc.).""" + # Handle role changes, nickname changes, etc. + pass +``` + +### Guild Events + +```python +@commands.Cog.listener() +async def on_guild_join(self, guild: discord.Guild) -> None: + """Handle bot joining a guild.""" + # Initialize guild config, send welcome message + pass + +@commands.Cog.listener() +async def on_guild_remove(self, guild: discord.Guild) -> None: + """Handle bot leaving a guild.""" + # Cleanup guild data + pass +``` + +### Command Events + +```python +@commands.Cog.listener("on_command") +async def on_command(self, ctx: commands.Context[Tux]) -> None: + """Handle command invocation.""" + # Logging, metrics, etc. + pass + +@commands.Cog.listener("on_command_completion") +async def on_command_completion( + self, + ctx: commands.Context[Tux], +) -> None: + """Handle command completion.""" + # Logging, metrics, etc. + pass + +@commands.Cog.listener("on_command_error") +async def on_command_error( + self, + ctx: commands.Context[Tux], + error: commands.CommandError, +) -> None: + """Handle command errors.""" + # Error handling, logging + pass +``` + +## Event Organization + +### Event Handler Cogs + +Create dedicated cogs for event handling: + +```python +class EventHandler(BaseCog): + """Centralized event handling.""" + + @commands.Cog.listener() + async def on_member_join(self, member: discord.Member) -> None: + # Handle join + pass + + @commands.Cog.listener() + async def on_member_remove(self, member: discord.Member) -> None: + # Handle leave + pass +``` + +✅ **GOOD:** Dedicated event handler cogs + +### Feature-Specific Events + +Events can be in feature cogs: + +```python +class Levels(BaseCog): + """Leveling system cog.""" + + @commands.Cog.listener() + async def on_message(self, message: discord.Message) -> None: + """Handle messages for XP calculation.""" + if message.author.bot: + return + # Calculate XP + pass +``` + +✅ **GOOD:** Events in feature cogs when relevant + +## Error Handling in Events + +### Try-Except Blocks + +```python +@commands.Cog.listener() +async def on_member_join(self, member: discord.Member) -> None: + """Handle member joins with error handling.""" + try: + # Event logic + await welcome_member(member) + except Exception as e: + logger.exception(f"Failed to handle member join: {e}") + # Don't let event errors crash the bot +``` + +✅ **GOOD:** Always handle errors in events + +❌ **BAD:** Unhandled exceptions + +```python +# ❌ BAD: Unhandled exceptions can crash the bot +@commands.Cog.listener() +async def on_member_join(self, member: discord.Member) -> None: + await welcome_member(member) # No error handling! +``` + +## Event Best Practices + +1. **Always use @commands.Cog.listener()** - Required decorator +2. **Type all parameters** - Use proper Discord types +3. **Handle errors** - Don't let event errors crash the bot +4. **Check bot messages** - Ignore bot messages where appropriate +5. **Assert guild when needed** - Use assert for type narrowing +6. **Log events** - Log important events for debugging +7. **Organize by feature** - Group related events together + +## Anti-Patterns + +1. ❌ **Missing listener decorator** - Always use @commands.Cog.listener() +2. ❌ **No error handling** - Always handle exceptions +3. ❌ **Processing bot messages** - Check message.author.bot +4. ❌ **No type hints** - Always type parameters +5. ❌ **Blocking operations** - Use async/await properly +6. ❌ **Unorganized events** - Group related events + +## See Also + +- @modules/cogs.mdc - Cog structure +- @modules/commands.mdc - Command patterns +- @error-handling/patterns.mdc - Error handling patterns +- @AGENTS.md - General coding standards +- @rules.mdc - Complete catalog of all rules and commands diff --git a/.cursor/rules/modules/interactions.mdc b/.cursor/rules/modules/interactions.mdc new file mode 100644 index 00000000..57e8d4e5 --- /dev/null +++ b/.cursor/rules/modules/interactions.mdc @@ -0,0 +1,298 @@ +--- +description: Discord interaction patterns, interaction responses, modals, components, and interaction handling for Tux +globs: src/tux/**/*.py +alwaysApply: false +--- + +# Discord Bot Interactions + +## Overview + +Tux handles Discord interactions (slash commands, buttons, selects, modals) with proper response patterns, error handling, and Components V2 support. Interactions should be properly typed and follow Discord's interaction lifecycle. + +## Interaction Types + +### Slash Commands + +Slash commands are handled through hybrid commands: + +```python +@commands.hybrid_command(name="command") +async def command( + self, + ctx: commands.Context[Tux], + member: discord.Member, +) -> None: + """Command description.""" + # Works for both slash and traditional commands + # ctx.interaction is available for slash commands + pass +``` + +✅ **GOOD:** Hybrid commands support both interaction and context + +### Button Interactions + +```python +from discord.ui import View, Button + +class MyView(View): + @discord.ui.button(label="Click Me", style=discord.ButtonStyle.primary) + async def button_callback( + self, + interaction: discord.Interaction, + button: discord.ui.Button, + ) -> None: + """Handle button click.""" + await interaction.response.send_message("Button clicked!") +``` + +✅ **GOOD:** Proper interaction response, type hints + +❌ **BAD:** Missing response or wrong response type + +```python +# ❌ BAD: Missing response +@discord.ui.button(label="Click") +async def button_callback(self, interaction, button): + # Missing await interaction.response... + pass +``` + +### Select Menu Interactions + +```python +from discord.ui import Select + +class MySelect(Select): + def __init__(self) -> None: + super().__init__( + placeholder="Choose an option...", + options=[ + discord.SelectOption(label="Option 1", value="1"), + discord.SelectOption(label="Option 2", value="2"), + ], + ) + + async def callback(self, interaction: discord.Interaction) -> None: + """Handle select menu selection.""" + await interaction.response.send_message( + f"You selected: {self.values[0]}" + ) +``` + +### Modal Interactions + +```python +from discord.ui import Modal, TextInput + +class MyModal(Modal, title="My Modal"): + name = TextInput( + label="Name", + placeholder="Enter your name", + required=True, + max_length=100, + ) + + async def on_submit(self, interaction: discord.Interaction) -> None: + """Handle modal submission.""" + await interaction.response.send_message( + f"Submitted: {self.name.value}" + ) +``` + +✅ **GOOD:** Proper modal structure, response handling + +## Interaction Responses + +### Immediate Response + +```python +# ✅ GOOD: Respond immediately (within 3 seconds) +await interaction.response.send_message("Response") +``` + +### Deferred Response + +For long-running operations: + +```python +# ✅ GOOD: Defer for long operations +await interaction.response.defer() + +# Do long operation +result = await long_operation() + +# Follow up with result +await interaction.followup.send(result) +``` + +❌ **BAD:** Not responding or responding too late + +```python +# ❌ BAD: Response after 3 seconds +await long_operation() # Takes 5 seconds +await interaction.response.send_message("Done") # Too late! +``` + +### Ephemeral Responses + +For private responses: + +```python +# ✅ GOOD: Ephemeral response (only visible to user) +await interaction.response.send_message( + "Private message", + ephemeral=True +) +``` + +### Response Types + +```python +# Send message +await interaction.response.send_message("Text") + +# Send embed +await interaction.response.send_message(embed=embed) + +# Send view/components +await interaction.response.send_message(view=view) + +# Send modal +await interaction.response.send_modal(modal) +``` + +## Interaction Validation + +### Author Validation + +```python +from tux.ui.views.config.callbacks import validate_author + +# ✅ GOOD: Validate interaction author +if not await validate_author( + interaction, + expected_author, + "You can't use this interaction." +): + return +``` + +### Data Validation + +```python +from tux.ui.views.config.callbacks import validate_interaction_data + +# ✅ GOOD: Validate interaction data +if not await validate_interaction_data(interaction): + return +``` + +## Interaction Error Handling + +### Try-Except Blocks + +```python +async def button_callback( + self, + interaction: discord.Interaction, + button: discord.ui.Button, +) -> None: + """Handle button click with error handling.""" + try: + # Interaction logic + await do_something() + await interaction.response.send_message("Success!") + except discord.HTTPException as e: + logger.error(f"HTTP error: {e}") + if not interaction.response.is_done(): + await interaction.response.send_message( + "An error occurred.", + ephemeral=True + ) + except Exception as e: + logger.exception("Interaction error") + # Handle error appropriately +``` + +### Error Handler Utility + +```python +from tux.ui.views.config.callbacks import handle_callback_error + +# ✅ GOOD: Use error handler utility +try: + # Interaction logic + pass +except Exception as e: + await handle_callback_error( + interaction, + e, + operation="button_click" + ) +``` + +## Components V2 + +Tux uses Components V2 (Discord.py 2.6+): + +```python +from tux.ui.views import LayoutView +from discord.ui import TextDisplay, ActionRow, Button + +class MyView(LayoutView): + text = TextDisplay("Hello", id=1234) + action_row = ActionRow() + + @action_row.button(label="Click") + async def btn( + self, + interaction: discord.Interaction, + button: discord.ui.Button, + ) -> None: + await interaction.response.send_message("Hi!") +``` + +✅ **GOOD:** Use LayoutView for Components V2 + +See @ui/cv2.mdc for detailed Components V2 patterns. + +## Interaction Context + +### Getting Context + +```python +from tux.core.context import get_interaction_context + +# ✅ GOOD: Get standardized context +context = get_interaction_context(interaction) +# Returns dict with user_id, command_name, guild_id, etc. +``` + +## Best Practices + +1. **Respond within 3 seconds** - Defer if operation takes longer +2. **Always handle errors** - Don't let interaction errors crash +3. **Validate authors** - Check interaction author when needed +4. **Use ephemeral for private** - Use ephemeral=True for private responses +5. **Type interactions** - Always type interaction parameters +6. **Use Components V2** - Use LayoutView for new components +7. **Handle timeouts** - Check if response is done before responding + +## Anti-Patterns + +1. ❌ **Missing response** - Always respond to interactions +2. ❌ **Response after timeout** - Defer for long operations +3. ❌ **No error handling** - Always handle exceptions +4. ❌ **Missing type hints** - Always type interaction parameters +5. ❌ **Not validating authors** - Validate when needed +6. ❌ **Using old components** - Use Components V2 + +## See Also + +- @modules/commands.mdc - Command patterns +- @ui/cv2.mdc - Components V2 detailed patterns +- @error-handling/patterns.mdc - Error handling patterns +- @AGENTS.md - General coding standards +- @rules.mdc - Complete catalog of all rules and commands diff --git a/.cursor/rules/modules/permissions.mdc b/.cursor/rules/modules/permissions.mdc new file mode 100644 index 00000000..e63717d7 --- /dev/null +++ b/.cursor/rules/modules/permissions.mdc @@ -0,0 +1,156 @@ +--- +description: Discord permission patterns, command permissions, role-based access control, and permission checks for Tux +globs: src/tux/modules/**/*.py, src/tux/core/**/*.py +alwaysApply: false +--- + +# Discord Bot Permissions + +## Overview + +Tux uses a database-driven permission system where command permissions are configured per-guild in the database. All commands use `@requires_command_permission()` decorator for dynamic permission checking. + +## Permission Decorator + +### Basic Usage + +```python +from tux.core.checks import requires_command_permission + +@commands.hybrid_command(name="command") +@commands.guild_only() +@requires_command_permission() # ✅ GOOD: Use permission decorator +async def command(self, ctx: commands.Context[Tux]) -> None: + """Command description.""" + # Permission checked automatically + pass +``` + +✅ **GOOD:** Always use requires_command_permission() decorator + +❌ **BAD:** Manual permission checks or missing decorator + +```python +# ❌ BAD: Manual permission check +@commands.hybrid_command(name="command") +async def command(self, ctx: commands.Context[Tux]) -> None: + if not await check_permission(ctx): + return # Use decorator instead! +``` + +### Permission Configuration + +Permissions are configured per-guild in the database: + +- **Permission Ranks**: Numeric ranks (higher = more permissions) +- **Role Assignments**: Roles mapped to permission ranks +- **Command Permissions**: Commands require specific ranks + +✅ **GOOD:** Database-driven, per-guild configuration + +❌ **BAD:** Hard-coded permissions + +## Permission System Architecture + +### Permission Ranks + +Permission ranks are stored in the database: + +```python +# Permission ranks are numeric +# Higher rank = more permissions +# Rank 0 = no permissions (default) +# Rank 100 = maximum permissions +``` + +### Role-to-Rank Mapping + +Roles are assigned to permission ranks: + +```python +# Example configuration: +# @Admin role → Rank 100 +# @Moderator role → Rank 50 +# @Member role → Rank 10 +``` + +### Command Requirements + +Commands require specific permission ranks: + +```python +# Commands are configured in database: +# /ban → Requires Rank 50 +# /warn → Requires Rank 30 +# /info → Requires Rank 0 (everyone) +``` + +## Permission Checking Flow + +1. **User invokes command** - Command is called +2. **Get user's rank** - Look up user's highest role rank +3. **Check command requirement** - Get required rank from database +4. **Compare ranks** - User rank >= command requirement +5. **Allow or deny** - Execute command or raise TuxPermissionDeniedError + +✅ **GOOD:** Automatic, database-driven checking + +## Permission Exceptions + +### TuxPermissionDeniedError + +Raised when permission check fails: + +```python +from tux.shared.exceptions import TuxPermissionDeniedError + +# Automatically raised by decorator if permission denied +# Handled by error handler cog +``` + +### Error Handling + +Error handler automatically sends permission denied message: + +```python +# Error handler sends appropriate message: +# "You don't have permission to use this command." +``` + +## Permission Best Practices + +1. **Always use decorator** - Use @requires_command_permission() +2. **Database-driven** - Don't hard-code permissions +3. **Per-guild configuration** - Each guild configures independently +4. **Safe defaults** - Commands denied by default if not configured +5. **Rank-based system** - Use numeric ranks for flexibility +6. **Role assignments** - Map roles to ranks, not individual commands + +## Anti-Patterns + +1. ❌ **Manual permission checks** - Use decorator +2. ❌ **Hard-coded permissions** - Use database configuration +3. ❌ **Missing decorator** - Always use requires_command_permission() +4. ❌ **Global permissions** - Configure per-guild +5. ❌ **Command-specific roles** - Use rank-based system + +## Permission Configuration Commands + +Guild administrators configure permissions through bot commands: + +```python +# Configuration commands (in config cog): +# /config ranks create +# /config ranks assign +# /config commands set +``` + +✅ **GOOD:** User-friendly configuration interface + +## See Also + +- @modules/commands.mdc - Command patterns with permissions +- @modules/cogs.mdc - Cog structure +- @database/models.mdc - Permission model patterns +- @AGENTS.md - General coding standards +- @rules.mdc - Complete catalog of all rules and commands diff --git a/.cursor/rules/rules.mdc b/.cursor/rules/rules.mdc index d1491f76..b8772c77 100644 --- a/.cursor/rules/rules.mdc +++ b/.cursor/rules/rules.mdc @@ -1,6 +1,128 @@ --- +description: Complete catalog of all Cursor rules and commands for the Tux project alwaysApply: true --- -# Rules +# Cursor Rules & Commands Index -See [AGENTS.md](../../AGENTS.md) for the coding agent rules. +Complete catalog of all rules and commands in the Tux project. + +## Rules + +### Core Rules + +- `core/tech-stack.mdc` - Tech stack, dependencies, project structure + +### Database Rules + +- `database/models.mdc` - SQLModel database model patterns +- `database/migrations.mdc` - Alembic migration patterns +- `database/controllers.mdc` - Database controller patterns +- `database/services.mdc` - DatabaseService patterns +- `database/queries.mdc` - Database query patterns and optimization + +### Modules Rules + +- `modules/cogs.mdc` - Discord bot cog structure +- `modules/commands.mdc` - Discord command patterns +- `modules/events.mdc` - Discord event handler patterns +- `modules/permissions.mdc` - Permission patterns +- `modules/interactions.mdc` - Interaction response patterns + +### Testing Rules + +- `testing/pytest.mdc` - Pytest configuration and patterns +- `testing/fixtures.mdc` - Fixture usage patterns +- `testing/markers.mdc` - Test marker conventions +- `testing/coverage.mdc` - Coverage requirements +- `testing/async.mdc` - Async testing patterns + +### Security Rules + +- `security/patterns.mdc` - Security patterns +- `security/secrets.mdc` - Secret management +- `security/validation.mdc` - Input validation +- `security/dependencies.mdc` - Dependency security + +### Error Handling Rules + +- `error-handling/patterns.mdc` - Error patterns +- `error-handling/logging.mdc` - Logging patterns (loguru) +- `error-handling/sentry.mdc` - Sentry integration +- `error-handling/user-feedback.mdc` - User-facing error messages + +### Documentation Rules + +- `docs/docs.mdc` - Master guide for documentation rules +- `docs/patterns.mdc` - Practical examples and templates +- `docs/principals.mdc` - Documentation frameworks (Diátaxis) +- `docs/structure.mdc` - Documentation organization +- `docs/style.mdc` - Writing standards +- `docs/syntax.mdc` - Zensical syntax reference +- `docs/zensical.mdc` - Zensical setup and deployment + +### UI Rules + +- `ui/cv2.mdc` - Discord.py Components V2 rules + +### Meta Rules + +- `meta/cursor-rules.mdc` - Cursor rules specification +- `meta/cursor-commands.mdc` - Cursor commands specification + +## Commands + +### Code Quality Commands + +- `code-quality/lint.md` - Lint and fix code +- `code-quality/refactor.md` - Refactor code +- `code-quality/review-existing-diffs.md` - Review git diffs + +### Testing Commands + +- `testing/write-unit-tests.md` - Write unit tests +- `testing/run-all-tests-and-fix.md` - Run all tests and fix failures +- `testing/test-coverage.md` - Generate and review coverage +- `testing/integration-tests.md` - Run integration tests + +### Database Commands + +- `database/migration.md` - Create and apply migrations +- `database/health.md` - Check database connection +- `database/reset.md` - Reset database safely + +### Discord Commands + +- `discord/create-module.md` - Create new Discord module/cog +- `discord/test-command.md` - Test Discord command locally +- `discord/sync-commands.md` - Sync slash commands to Discord + +### Security Commands + +- `security/security-review.md` - Comprehensive security review + +### Debugging Commands + +- `debugging/debug.md` - Debug Discord bot issues + +### Error Handling Commands + +- `error-handling/add-error-handling.md` - Add error handling + +### Documentation Commands + +- `documentation/generate-docs.md` - Generate API documentation +- `documentation/update-docs.md` - Update documentation for changes +- `documentation/docs-serve.md` - Serve documentation locally + +### Development Commands + +- `development/setup-project.md` - Initial project setup +- `development/docker-up.md` - Start Docker services +- `development/docker-down.md` - Stop Docker services + +## See Also + +- [AGENTS.md](../../AGENTS.md) - High-level project overview and standards +- [README.md](../README.md) - System overview +- [Creating Cursor Rules Guide](../../docs/content/developer/guides/creating-cursor-rules.md) - Guide for creating rules +- [Creating Cursor Commands Guide](../../docs/content/developer/guides/creating-cursor-commands.md) - Guide for creating commands diff --git a/.cursor/rules/security/dependencies.mdc b/.cursor/rules/security/dependencies.mdc new file mode 100644 index 00000000..421079de --- /dev/null +++ b/.cursor/rules/security/dependencies.mdc @@ -0,0 +1,101 @@ +--- +description: Dependency security, dependency updates, vulnerability scanning, and dependency management for Tux +globs: pyproject.toml, uv.lock, **/requirements*.txt +alwaysApply: false +--- + +# Dependency Security + +## Overview + +Tux uses `uv` for dependency management with locked versions in `uv.lock`. Dependencies should be kept up to date and scanned for vulnerabilities. + +## Dependency Management + +### Using uv + +```bash +# ✅ GOOD: Use uv for dependency management +uv sync # Install dependencies +uv add package-name # Add new dependency +uv remove package-name # Remove dependency +uv lock # Update lock file +``` + +❌ **BAD:** Manual dependency management + +```bash +# ❌ BAD: Don't manually edit pyproject.toml without uv +# Always use uv commands +``` + +### Lock File + +```bash +# ✅ GOOD: Commit uv.lock file +git add uv.lock +git commit -m "chore: update dependencies" +``` + +✅ **GOOD:** Lock file ensures reproducible builds + +## Dependency Updates + +### Regular Updates + +```bash +# ✅ GOOD: Regularly update dependencies +uv lock --upgrade # Update all dependencies +uv lock --upgrade-package package-name # Update specific package +``` + +### Security Updates + +```bash +# ✅ GOOD: Check for security updates +uv lock --upgrade # Updates to latest compatible versions +# Review changelogs for security fixes +``` + +## Vulnerability Scanning + +### Manual Review + +```bash +# ✅ GOOD: Review dependency changelogs +# Check GitHub security advisories +# Review package release notes +``` + +### Automated Scanning + +```bash +# ✅ GOOD: Use automated tools +# Check GitHub Dependabot alerts +# Review security advisories +``` + +## Dependency Best Practices + +1. **Use uv** - Always use uv for dependency management +2. **Commit lock file** - Always commit uv.lock +3. **Regular updates** - Update dependencies regularly +4. **Review updates** - Review changelogs before updating +5. **Security patches** - Apply security patches immediately +6. **Minimal dependencies** - Only add necessary dependencies +7. **Version pinning** - Use locked versions in production + +## Anti-Patterns + +1. ❌ **Outdated dependencies** - Keep dependencies updated +2. ❌ **Ignoring security patches** - Apply security updates +3. ❌ **Too many dependencies** - Minimize dependencies +4. ❌ **Unpinned versions** - Use lock file +5. ❌ **Not reviewing updates** - Review before updating +6. ❌ **Manual edits** - Use uv commands + +## See Also + +- @core/tech-stack.mdc - Dependency list +- @AGENTS.md - General coding standards +- @rules.mdc - Complete catalog of all rules and commands diff --git a/.cursor/rules/security/patterns.mdc b/.cursor/rules/security/patterns.mdc new file mode 100644 index 00000000..a2fc8bf8 --- /dev/null +++ b/.cursor/rules/security/patterns.mdc @@ -0,0 +1,178 @@ +--- +description: Security patterns, secure coding practices, and security best practices for Tux +globs: src/tux/**/*.py +alwaysApply: false +--- + +# Security Patterns + +## Overview + +Tux follows security best practices for Discord bot operations, database access, and user input handling. All code should follow secure coding patterns. + +## Input Validation + +### Validate All Inputs + +```python +# ✅ GOOD: Validate user input +async def command( + self, + ctx: commands.Context[Tux], + member: discord.Member, + reason: str, +) -> None: + # Validate reason length + if len(reason) > 500: + await ctx.send("Reason too long (max 500 characters)") + return + + # Validate member + if member.bot: + await ctx.send("Cannot act on bots") + return +``` + +❌ **BAD:** Trusting user input without validation + +```python +# ❌ BAD: No validation +async def command(self, ctx, member, reason): + # Directly use user input - dangerous! + await do_action(member, reason) +``` + +### Sanitize Sensitive Data + +```python +from tux.services.sentry.handlers import _sanitize_sensitive_data + +# ✅ GOOD: Sanitize before logging +log_message = _sanitize_sensitive_data(f"Connecting to {database_url}") +logger.info(log_message) +``` + +❌ **BAD:** Logging sensitive data + +```python +# ❌ BAD: Logging sensitive data +logger.info(f"Database URL: {database_url}") # Contains password! +``` + +## Permission Checks + +### Always Check Permissions + +```python +# ✅ GOOD: Use permission decorator +@requires_command_permission() +async def command(self, ctx: commands.Context[Tux]) -> None: + # Permission checked automatically + pass +``` + +❌ **BAD:** Manual or missing permission checks + +```python +# ❌ BAD: Missing permission check +async def command(self, ctx: commands.Context[Tux]) -> None: + # Anyone can use this! + pass +``` + +### Validate Interaction Authors + +```python +from tux.ui.views.config.callbacks import validate_author + +# ✅ GOOD: Validate interaction author +async def button_callback( + self, + interaction: discord.Interaction, + button: discord.ui.Button, +) -> None: + if not await validate_author( + interaction, + expected_author, + "You can't use this interaction." + ): + return +``` + +## Database Security + +### Parameterized Queries + +SQLModel automatically uses parameterized queries: + +```python +# ✅ GOOD: SQLModel uses parameterized queries +stmt = select(MyModel).where(MyModel.id == user_id) +result = await session.execute(stmt) +``` + +❌ **BAD:** String concatenation for queries + +```python +# ❌ BAD: SQL injection risk (SQLModel prevents this, but don't do it) +query = f"SELECT * FROM table WHERE id = {user_id}" # Dangerous! +``` + +### Connection Security + +```python +# ✅ GOOD: Use secure connection settings +database_url = "postgresql+psycopg://user:password@host:port/db" +# Password from environment, not hard-coded +``` + +## Error Handling + +### Don't Leak Information + +```python +# ✅ GOOD: Generic error messages for users +try: + await sensitive_operation() +except Exception as e: + logger.exception("Operation failed") # Detailed log + await ctx.send("An error occurred.") # Generic message +``` + +❌ **BAD:** Exposing internal details + +```python +# ❌ BAD: Exposing internal errors +try: + await operation() +except Exception as e: + await ctx.send(f"Error: {e}") # Exposes internal details! +``` + +## Best Practices + +1. **Validate all inputs** - Never trust user input +2. **Sanitize sensitive data** - Before logging or displaying +3. **Check permissions** - Always verify user permissions +4. **Use parameterized queries** - SQLModel handles this automatically +5. **Secure connections** - Use environment variables for credentials +6. **Generic error messages** - Don't leak internal details +7. **Log security events** - Log permission denials, etc. + +## Anti-Patterns + +1. ❌ **Trusting user input** - Always validate +2. ❌ **Logging sensitive data** - Sanitize before logging +3. ❌ **Missing permission checks** - Always check permissions +4. ❌ **Exposing errors** - Use generic error messages +5. ❌ **Hard-coded secrets** - Use environment variables +6. ❌ **SQL injection** - Use parameterized queries (SQLModel) + +## See Also + +- @security/secrets.mdc - Secret management +- @security/validation.mdc - Input validation patterns +- @modules/permissions.mdc - Permission patterns +- @error-handling/patterns.mdc - Error handling patterns +- @AGENTS.md - General coding standards +- @rules.mdc - Complete catalog of all rules and commands diff --git a/.cursor/rules/security/secrets.mdc b/.cursor/rules/security/secrets.mdc new file mode 100644 index 00000000..10226c15 --- /dev/null +++ b/.cursor/rules/security/secrets.mdc @@ -0,0 +1,159 @@ +--- +description: Secret management, environment variables, credential handling, and secure configuration for Tux +globs: src/tux/**/*.py, .env*, config/*.toml, config/*.yaml, config/*.json +alwaysApply: false +--- + +# Secret Management + +## Overview + +Tux uses environment variables and Pydantic Settings for secure secret management. Secrets are never hard-coded and are loaded from environment variables with proper validation. + +## Environment Variables + +### Configuration Priority + +Secrets are loaded in priority order: + +1. **Environment variables** (highest priority) +2. `.env` file +3. `config.toml` file +4. `config.yaml` file +5. `config.json` file +6. Default values (lowest priority) + +✅ **GOOD:** Environment variables override file config + +### Using Environment Variables + +```python +from tux.shared.config import CONFIG + +# ✅ GOOD: Access secrets via CONFIG +bot_token = CONFIG.BOT_TOKEN +database_url = CONFIG.database_url +``` + +❌ **BAD:** Hard-coding secrets or using os.getenv directly + +```python +# ❌ BAD: Hard-coded secret +BOT_TOKEN = "hard-coded-token-here" + +# ❌ BAD: Direct os.getenv (use CONFIG instead) +token = os.getenv("BOT_TOKEN") +``` + +## Secret Validation + +### Password Validation + +Tux validates database passwords on startup: + +```python +# ✅ GOOD: Validation prevents weak passwords +def validate_environment() -> None: + db_password = os.getenv("POSTGRES_PASSWORD", "") + weak_passwords = ["password", "admin", "postgres", "123456"] + + if db_password in weak_passwords: + raise ValueError("Cannot use insecure password!") +``` + +✅ **GOOD:** Validation prevents insecure defaults + +### Secret Strength + +```python +# ✅ GOOD: Enforce minimum password length +if len(db_password) < 12: + warnings.warn("Password too short (<12 chars)") +``` + +## Secret Sanitization + +### Logging Secrets + +```python +from tux.services.sentry.handlers import _sanitize_sensitive_data + +# ✅ GOOD: Sanitize before logging +log_message = _sanitize_sensitive_data(f"URL: {database_url}") +logger.info(log_message) +# Output: "URL: postgresql://***:***@host:port/db" +``` + +❌ **BAD:** Logging secrets directly + +```python +# ❌ BAD: Logging secrets +logger.info(f"Database URL: {database_url}") # Contains password! +``` + +### Sentry Sanitization + +Sentry automatically sanitizes sensitive data: + +```python +# ✅ GOOD: Sentry sanitizes automatically +from tux.services.sentry.handlers import _sanitize_event_data + +# Event data is sanitized before sending to Sentry +``` + +## Configuration Files + +### .env File + +```bash +# ✅ GOOD: .env file for local development +BOT_TOKEN=your-token-here +POSTGRES_PASSWORD=strong-password-here +``` + +✅ **GOOD:** .env file is gitignored + +### Config Files + +```toml +# ✅ GOOD: Config files for non-sensitive defaults +# config.toml (example, not committed) +[bot] +token = "" # Empty default, use environment variable +``` + +❌ **BAD:** Committing secrets to config files + +```toml +# ❌ BAD: Secrets in committed files +[bot] +token = "actual-secret-token" # Never commit this! +``` + +## Best Practices + +1. **Use environment variables** - For all secrets +2. **Access via CONFIG** - Use CONFIG object, not os.getenv +3. **Validate secrets** - Check password strength, etc. +4. **Sanitize before logging** - Never log secrets +5. **Gitignore .env** - Never commit .env files +6. **Empty defaults** - Use empty strings for secrets in config files +7. **Document requirements** - Document required environment variables + +## Anti-Patterns + +1. ❌ **Hard-coded secrets** - Always use environment variables +2. ❌ **Committing secrets** - Never commit .env or secrets +3. ❌ **Logging secrets** - Sanitize before logging +4. ❌ **Weak passwords** - Enforce password strength +5. ❌ **Direct os.getenv** - Use CONFIG instead +6. ❌ **Secrets in code** - Never hard-code secrets + +## See Also + +- @security/patterns.mdc - Security patterns +- @security/validation.mdc - Input validation +- @core/tech-stack.mdc - Configuration system +- @AGENTS.md - General coding standards +- @rules.mdc - Complete catalog of all rules and commands diff --git a/.cursor/rules/security/validation.mdc b/.cursor/rules/security/validation.mdc new file mode 100644 index 00000000..3a26a7c0 --- /dev/null +++ b/.cursor/rules/security/validation.mdc @@ -0,0 +1,191 @@ +--- +description: Input validation patterns, data sanitization, and validation best practices for Tux +globs: src/tux/**/*.py +alwaysApply: false +--- + +# Input Validation + +## Overview + +Tux validates all user input to prevent security vulnerabilities, data corruption, and errors. Validation should happen early and fail fast. + +## Command Input Validation + +### Parameter Validation + +```python +# ✅ GOOD: Validate command parameters +async def command( + self, + ctx: commands.Context[Tux], + member: discord.Member, + reason: str, +) -> None: + # Validate reason length + if len(reason) > 500: + embed = EmbedCreator.error( + title="Invalid Input", + description="Reason must be 500 characters or less." + ) + await ctx.send(embed=embed) + return + + # Validate member + if member.bot: + embed = EmbedCreator.error( + title="Invalid Target", + description="Cannot act on bots." + ) + await ctx.send(embed=embed) + return + + # Proceed with validated input + await do_action(member, reason) +``` + +❌ **BAD:** No validation + +```python +# ❌ BAD: No validation +async def command(self, ctx, member, reason): + # Directly use input - dangerous! + await do_action(member, reason) +``` + +### Type Validation + +```python +# ✅ GOOD: Use type hints and validate types +async def command( + self, + ctx: commands.Context[Tux], + count: int, +) -> None: + # Validate range + if count < 1 or count > 100: + await ctx.send("Count must be between 1 and 100") + return +``` + +## Interaction Validation + +### Author Validation + +```python +from tux.ui.views.config.callbacks import validate_author + +# ✅ GOOD: Validate interaction author +async def button_callback( + self, + interaction: discord.Interaction, + button: discord.ui.Button, +) -> None: + if not await validate_author( + interaction, + expected_author, + "You can't use this interaction." + ): + return +``` + +### Data Validation + +```python +from tux.ui.views.config.callbacks import validate_interaction_data + +# ✅ GOOD: Validate interaction data +async def callback(self, interaction: discord.Interaction) -> None: + if not await validate_interaction_data(interaction): + return +``` + +## Database Input Validation + +### Model Validation + +SQLModel automatically validates using Pydantic: + +```python +# ✅ GOOD: SQLModel validates automatically +guild = Guild( + id=guild_id, + case_count=count, # Validated by Pydantic constraints +) +# Raises ValidationError if constraints violated +``` + +### Query Parameter Validation + +```python +# ✅ GOOD: Validate query parameters +async def get_cases( + self, + guild_id: int, + limit: int = 10, +) -> list[Case]: + # Validate limit + if limit < 1 or limit > 100: + raise ValueError("Limit must be between 1 and 100") + + # SQLModel uses parameterized queries automatically + stmt = select(Case).where(Case.guild_id == guild_id).limit(limit) + return await session.execute(stmt) +``` + +## String Validation + +### Length Validation + +```python +# ✅ GOOD: Validate string length +def validate_reason(reason: str) -> bool: + if len(reason) < 1: + return False + if len(reason) > 500: + return False + return True +``` + +### Content Validation + +```python +# ✅ GOOD: Validate string content +import re + +def validate_prefix(prefix: str) -> bool: + # Only allow alphanumeric and common symbols + if not re.match(r"^[a-zA-Z0-9!@#$%^&*()_+\-=\[\]{};':\"\\|,.<>\/?]+$", prefix): + return False + if len(prefix) > 3: + return False + return True +``` + +## Best Practices + +1. **Validate early** - Validate as soon as input is received +2. **Fail fast** - Return error immediately on validation failure +3. **Use type hints** - Leverage Python type system +4. **Validate ranges** - Check numeric ranges +5. **Validate length** - Check string lengths +6. **Validate format** - Use regex for format validation +7. **User-friendly errors** - Provide clear error messages + +## Anti-Patterns + +1. ❌ **No validation** - Always validate user input +2. ❌ **Late validation** - Validate early, not late +3. ❌ **Silent failures** - Return clear error messages +4. ❌ **Trusting input** - Never trust user input +5. ❌ **Weak validation** - Validate thoroughly +6. ❌ **Generic errors** - Provide specific error messages + +## See Also + +- @security/patterns.mdc - Security patterns +- @modules/commands.mdc - Command patterns +- @modules/interactions.mdc - Interaction patterns +- @database/models.mdc - Model validation +- @AGENTS.md - General coding standards +- @rules.mdc - Complete catalog of all rules and commands diff --git a/.cursor/rules/testing/async.mdc b/.cursor/rules/testing/async.mdc new file mode 100644 index 00000000..cd00a458 --- /dev/null +++ b/.cursor/rules/testing/async.mdc @@ -0,0 +1,159 @@ +--- +description: Async testing patterns, async fixtures, async test execution, and asyncio testing for Tux +globs: tests/**/*.py +alwaysApply: false +--- + +# Async Testing + +## Overview + +Tux uses pytest-asyncio for async test support. All database operations and Discord bot operations are async, so most tests are async. + +## Async Test Pattern + +### Basic Async Test + +```python +import pytest + +@pytest.mark.asyncio +@pytest.mark.unit +async def test_async_function(db_service: DatabaseService) -> None: + """Test async function.""" + result = await async_function() + assert result is not None +``` + +✅ **GOOD:** asyncio marker, async function, await calls + +❌ **BAD:** Missing asyncio marker or not using await + +```python +# ❌ BAD: Missing asyncio marker +async def test_async_function(db_service): + result = await async_function() # Will fail! + +# ❌ BAD: Not using await +@pytest.mark.asyncio +async def test_async_function(db_service): + result = async_function() # Missing await! +``` + +## Async Fixtures + +### Async Fixture Pattern + +```python +@pytest.fixture(scope="function") +async def db_service(pglite_engine): + """Async database service fixture.""" + service = DatabaseService(echo=False) + service._engine = pglite_engine + yield service + # Cleanup +``` + +✅ **GOOD:** Async fixtures for async setup/teardown + +### Fixture Dependencies + +```python +# ✅ GOOD: Async fixtures can depend on other async fixtures +@pytest.fixture(scope="session") +async def pglite_async_manager(): + manager = SQLAlchemyAsyncPGliteManager() + manager.start() + yield manager + manager.stop() + +@pytest.fixture(scope="function") +async def pglite_engine(pglite_async_manager): + # Depends on async fixture + engine = create_async_engine(...) + yield engine +``` + +## Async Database Operations + +### Database Queries + +```python +@pytest.mark.asyncio +@pytest.mark.unit +async def test_database_query(db_service: DatabaseService) -> None: + """Test async database query.""" + async with db_service.session() as session: + result = await session.execute(select(MyModel)) + models = result.scalars().all() + assert len(models) == 0 +``` + +✅ **GOOD:** Use async session context manager + +### Controller Operations + +```python +@pytest.mark.asyncio +@pytest.mark.unit +async def test_controller_operation(guild_controller: GuildController) -> None: + """Test async controller operation.""" + guild = await guild_controller.create(id=123456789) + assert guild.id == 123456789 +``` + +✅ **GOOD:** Use async controller methods + +## Async Error Handling + +### Exception Testing + +```python +@pytest.mark.asyncio +@pytest.mark.unit +async def test_async_error_handling(db_service: DatabaseService) -> None: + """Test async error handling.""" + with pytest.raises(ValueError): + await async_function_that_raises() +``` + +✅ **GOOD:** Use pytest.raises for async exceptions + +## Async Timeouts + +### Timeout Testing + +```python +@pytest.mark.asyncio +@pytest.mark.timeout(5) # 5 second timeout +async def test_with_timeout(db_service: DatabaseService) -> None: + """Test with timeout.""" + await long_operation() +``` + +✅ **GOOD:** Use timeout marker for long operations + +## Best Practices + +1. **Always use asyncio marker** - Required for async tests +2. **Use async fixtures** - For async setup/teardown +3. **Await all async calls** - Don't forget await +4. **Use async context managers** - For async resources +5. **Handle async errors** - Use pytest.raises properly +6. **Test async operations** - Test all async code paths + +## Anti-Patterns + +1. ❌ **Missing asyncio marker** - Always use @pytest.mark.asyncio +2. ❌ **Not awaiting** - Always await async calls +3. ❌ **Sync fixtures for async** - Use async fixtures +4. ❌ **Blocking in async** - Don't use blocking operations +5. ❌ **Not testing async errors** - Test error paths + +## See Also + +- @testing/pytest.mdc - Pytest configuration +- @testing/fixtures.mdc - Fixture patterns +- @testing/markers.mdc - Test markers +- @AGENTS.md - General coding standards +- @rules.mdc - Complete catalog of all rules and commands diff --git a/.cursor/rules/testing/coverage.mdc b/.cursor/rules/testing/coverage.mdc new file mode 100644 index 00000000..a922ae60 --- /dev/null +++ b/.cursor/rules/testing/coverage.mdc @@ -0,0 +1,134 @@ +--- +description: Test coverage requirements, coverage reporting, coverage goals, and coverage best practices for Tux +globs: tests/**/*.py, pyproject.toml +alwaysApply: false +--- + +# Test Coverage + +## Overview + +Tux uses pytest-cov for coverage reporting with multiple output formats. Coverage is automatically generated for all test runs and should be maintained at high levels. + +## Coverage Configuration + +Coverage is configured in `pyproject.toml`: + +```toml +[tool.pytest.ini_options.addopts] +--cov=src/tux +--cov-report=term-missing +--cov-report=xml +--cov-report=json +--cov-report=lcov +--cov-branch +``` + +✅ **GOOD:** Multiple coverage formats for different tools + +## Coverage Reports + +### Terminal Report + +```bash +# ✅ GOOD: Terminal report shows missing lines +uv run test all +# Shows coverage percentage and missing lines +``` + +### HTML Report + +```bash +# ✅ GOOD: HTML report for detailed analysis +uv run test html +# Generates htmlcov/index.html +``` + +### XML/JSON Reports + +```bash +# ✅ GOOD: Machine-readable formats +# Coverage XML for CI/CD +# Coverage JSON for tools +``` + +## Coverage Goals + +### Target Coverage + +- **Overall:** Aim for 80%+ coverage +- **Critical paths:** 100% coverage (database, services, core) +- **New code:** 100% coverage requirement +- **Legacy code:** Improve gradually + +✅ **GOOD:** High coverage on critical code + +### Coverage by Type + +- **Unit tests:** High coverage (80%+) +- **Integration tests:** Moderate coverage (60%+) +- **E2E tests:** Lower coverage (40%+) + +## Coverage Best Practices + +1. **Test new code** - Always add tests for new features +2. **Cover edge cases** - Test error paths and edge cases +3. **Cover critical paths** - Ensure 100% coverage on critical code +4. **Review coverage** - Regularly review coverage reports +5. **Improve gradually** - Improve coverage on legacy code over time +6. **Use branch coverage** - Enable --cov-branch for branch coverage + +## Coverage Exclusions + +Some code may be excluded from coverage: + +```python +# pragma: no cover +def debug_function(): + # Excluded from coverage + pass +``` + +✅ **GOOD:** Exclude only when necessary (debug code, unreachable code) + +## Coverage Analysis + +### Identifying Gaps + +```bash +# ✅ GOOD: Use HTML report to identify gaps +uv run test html +# Open htmlcov/index.html in browser +# Review uncovered lines +``` + +### Improving Coverage + +1. **Identify uncovered code** - Review coverage reports +2. **Add missing tests** - Write tests for uncovered code +3. **Test edge cases** - Cover error paths +4. **Test integration points** - Cover service interactions + +## Best Practices + +1. **Maintain high coverage** - Aim for 80%+ overall +2. **Cover critical paths** - 100% on critical code +3. **Review regularly** - Check coverage reports +4. **Test new code** - Always add tests for new features +5. **Use multiple formats** - Terminal, HTML, XML, JSON +6. **Enable branch coverage** - Test all code paths + +## Anti-Patterns + +1. ❌ **Low coverage** - Maintain high coverage +2. ❌ **Not testing new code** - Always add tests +3. ❌ **Ignoring coverage reports** - Review regularly +4. ❌ **Excluding too much** - Only exclude when necessary +5. ❌ **Not testing edge cases** - Cover error paths + +## See Also + +- @testing/pytest.mdc - Pytest configuration +- @testing/fixtures.mdc - Fixture patterns +- @AGENTS.md - General coding standards +- @rules.mdc - Complete catalog of all rules and commands diff --git a/.cursor/rules/testing/fixtures.mdc b/.cursor/rules/testing/fixtures.mdc new file mode 100644 index 00000000..4711bf76 --- /dev/null +++ b/.cursor/rules/testing/fixtures.mdc @@ -0,0 +1,206 @@ +--- +description: Pytest fixture patterns, fixture scopes, fixture organization, and fixture usage for Tux +globs: tests/**/*.py +alwaysApply: false +--- + +# Pytest Fixtures + +## Overview + +Tux uses pytest fixtures organized in `tests/fixtures/` for database setup, test data, and mocks. Fixtures are automatically discovered via `pytest_plugins` in `conftest.py`. + +## Fixture Organization + +### Fixture Modules + +Fixtures are organized by category: + +``` +tests/fixtures/ +├── database_fixtures.py # Database and PGlite fixtures +├── test_data_fixtures.py # Sample data and constants +└── sentry_fixtures.py # Sentry and Discord mocks +``` + +✅ **GOOD:** Organized by category, clear naming + +### Fixture Registration + +Fixtures are registered in `conftest.py`: + +```python +# ✅ GOOD: Register fixtures via pytest_plugins +pytest_plugins = [ + "tests.fixtures.database_fixtures", + "tests.fixtures.test_data_fixtures", + "tests.fixtures.sentry_fixtures", +] +``` + +## Fixture Scopes + +### Session Scope + +For expensive setup shared across all tests: + +```python +@pytest.fixture(scope="session") +async def pglite_async_manager(): + """Session-scoped PGlite manager - shared across tests.""" + manager = SQLAlchemyAsyncPGliteManager() + manager.start() + yield manager + manager.stop() +``` + +✅ **GOOD:** Session scope for expensive resources + +### Function Scope + +For fresh state per test: + +```python +@pytest.fixture(scope="function") +async def db_service(pglite_engine): + """Function-scoped database service - fresh per test.""" + service = DatabaseService(echo=False) + service._engine = pglite_engine + yield service + # Cleanup +``` + +✅ **GOOD:** Function scope for test isolation + +## Database Fixtures + +### PGlite Manager + +```python +@pytest.fixture(scope="session") +async def pglite_async_manager(): + """Session-scoped PGlite async manager.""" + manager = SQLAlchemyAsyncPGliteManager() + try: + manager.start() + if not manager.wait_for_ready(max_retries=10, delay=0.1): + raise RuntimeError("PGlite failed to become ready") + yield manager + finally: + manager.stop() +``` + +✅ **GOOD:** Proper startup/teardown, error handling + +### Database Service + +```python +@pytest.fixture(scope="function") +async def db_service(pglite_engine): + """DatabaseService with fresh database per test.""" + service = DatabaseService(echo=False) + service._engine = pglite_engine + service._session_factory = async_sessionmaker(...) + yield service + # Cleanup handled automatically +``` + +### Controllers + +```python +@pytest.fixture(scope="function") +async def guild_controller(db_service: DatabaseService) -> GuildController: + """Guild controller fixture.""" + return GuildController(Guild, db_service) +``` + +✅ **GOOD:** Type hints, proper dependency injection + +## Test Data Fixtures + +### Constants + +```python +# Test constants +TEST_GUILD_ID = 123456789012345678 +TEST_USER_ID = 987654321098765432 +TEST_CHANNEL_ID = 876543210987654321 +``` + +✅ **GOOD:** Centralized test constants + +### Sample Data + +```python +@pytest.fixture(scope="function") +async def sample_guild(guild_controller: GuildController) -> Guild: + """Sample guild for testing.""" + return await guild_controller.insert_guild_by_id(TEST_GUILD_ID) +``` + +✅ **GOOD:** Reusable test data fixtures + +## Mock Fixtures + +### Sentry Mocks + +```python +@pytest.fixture +def mock_sentry_sdk(): + """Mock Sentry SDK.""" + with patch("sentry_sdk.init") as mock_init: + yield mock_init +``` + +✅ **GOOD:** Proper mocking with context managers + +## Fixture Dependencies + +### Fixture Chains + +```python +# ✅ GOOD: Fixtures can depend on other fixtures +@pytest.fixture(scope="session") +async def pglite_async_manager(): + # Session-scoped + yield manager + +@pytest.fixture(scope="function") +async def pglite_engine(pglite_async_manager): + # Depends on session fixture + yield engine + +@pytest.fixture(scope="function") +async def db_service(pglite_engine): + # Depends on function fixture + yield service +``` + +✅ **GOOD:** Proper fixture dependency chain + +## Best Practices + +1. **Organize by category** - Group related fixtures +2. **Use appropriate scope** - Session for expensive, function for isolation +3. **Type fixtures** - Always type fixture return values +4. **Document fixtures** - Docstrings explain fixture purpose +5. **Cleanup properly** - Use try/finally or yield for cleanup +6. **Reuse fixtures** - Don't duplicate fixture logic +7. **Use constants** - Centralize test constants + +## Anti-Patterns + +1. ❌ **Wrong scope** - Don't use session for test-specific data +2. ❌ **No cleanup** - Always clean up resources +3. ❌ **Missing type hints** - Always type fixtures +4. ❌ **Duplicated fixtures** - Reuse existing fixtures +5. ❌ **Hard-coded values** - Use constants and fixtures +6. ❌ **Fixture dependencies** - Don't create circular dependencies + +## See Also + +- @testing/pytest.mdc - Pytest configuration +- @testing/markers.mdc - Test markers +- @testing/async.mdc - Async fixture patterns +- @AGENTS.md - General coding standards +- @rules.mdc - Complete catalog of all rules and commands diff --git a/.cursor/rules/testing/markers.mdc b/.cursor/rules/testing/markers.mdc new file mode 100644 index 00000000..c25641b6 --- /dev/null +++ b/.cursor/rules/testing/markers.mdc @@ -0,0 +1,194 @@ +--- +description: Pytest test markers, marker conventions, marker usage, and test categorization for Tux +globs: tests/**/*.py +alwaysApply: false +--- + +# Pytest Test Markers + +## Overview + +Tux uses pytest markers to categorize tests by type, speed, and requirements. Markers enable selective test execution and proper test organization. + +## Available Markers + +Markers are defined in `pyproject.toml`: + +```toml +[tool.pytest.ini_options.markers] +unit = "Unit tests (uses py-pglite)" +integration = "Integration tests (uses py-pglite)" +slow = "Slow tests (>5 seconds)" +database = "Tests requiring database access" +async = "Async tests" +``` + +## Marker Usage + +### Unit Tests + +```python +@pytest.mark.unit +async def test_model_creation(db_service: DatabaseService) -> None: + """Unit test for model creation.""" + # Fast, isolated test + pass +``` + +✅ **GOOD:** Unit tests are fast and isolated + +**Characteristics:** + +- Fast execution (<1 second) +- Isolated (no external dependencies) +- Uses py-pglite for database +- Tests single unit of code + +### Integration Tests + +```python +@pytest.mark.integration +async def test_database_service_integration(db_service: DatabaseService) -> None: + """Integration test for database service.""" + # Tests multiple components together + pass +``` + +✅ **GOOD:** Integration tests verify component interactions + +**Characteristics:** + +- Tests multiple components +- Uses py-pglite for database +- May test service layer integration +- Still relatively fast + +### Slow Tests + +```python +@pytest.mark.slow +@pytest.mark.integration +async def test_full_workflow(db_service: DatabaseService) -> None: + """Slow integration test for full workflow.""" + # Takes >5 seconds + pass +``` + +✅ **GOOD:** Mark slow tests for selective execution + +**Characteristics:** + +- Execution time >5 seconds +- May test complex workflows +- Can be skipped in quick test runs + +### Database Tests + +```python +@pytest.mark.database +@pytest.mark.unit +async def test_database_query(db_service: DatabaseService) -> None: + """Database test.""" + # Requires database access + pass +``` + +✅ **GOOD:** Mark tests that require database + +**Characteristics:** + +- Requires database access +- Uses py-pglite fixtures +- Tests database operations + +### Async Tests + +```python +@pytest.mark.asyncio +@pytest.mark.unit +async def test_async_function(db_service: DatabaseService) -> None: + """Async test.""" + # Async test function + result = await async_function() + assert result is not None +``` + +✅ **GOOD:** Mark async tests with asyncio marker + +**Characteristics:** + +- Uses async/await +- Requires asyncio marker +- Tests async code + +## Multiple Markers + +Tests can have multiple markers: + +```python +@pytest.mark.unit +@pytest.mark.database +@pytest.mark.asyncio +async def test_async_database_operation(db_service: DatabaseService) -> None: + """Async unit test with database.""" + pass +``` + +✅ **GOOD:** Use multiple markers when appropriate + +## Marker Combinations + +### Common Combinations + +```python +# Unit test with database +@pytest.mark.unit +@pytest.mark.database + +# Integration test with database +@pytest.mark.integration +@pytest.mark.database + +# Slow integration test +@pytest.mark.slow +@pytest.mark.integration + +# Async unit test +@pytest.mark.asyncio +@pytest.mark.unit +``` + +## Running Tests by Marker + +```bash +# ✅ GOOD: Run tests by marker +uv run pytest -m unit # Only unit tests +uv run pytest -m integration # Only integration tests +uv run pytest -m "not slow" # Exclude slow tests +uv run pytest -m "unit and database" # Unit tests with database +``` + +## Best Practices + +1. **Always use markers** - Mark all tests appropriately +2. **Use multiple markers** - Combine markers when needed +3. **Be specific** - Use most specific marker combination +4. **Mark slow tests** - Always mark tests >5 seconds +5. **Mark async tests** - Always use asyncio marker +6. **Mark database tests** - Mark tests requiring database + +## Anti-Patterns + +1. ❌ **Missing markers** - Always mark tests +2. ❌ **Wrong markers** - Use appropriate markers +3. ❌ **Not marking slow tests** - Always mark slow tests +4. ❌ **Missing asyncio** - Always mark async tests +5. ❌ **Over-marking** - Don't use unnecessary markers + +## See Also + +- @testing/pytest.mdc - Pytest configuration +- @testing/fixtures.mdc - Fixture patterns +- @testing/async.mdc - Async testing patterns +- @AGENTS.md - General coding standards +- @rules.mdc - Complete catalog of all rules and commands diff --git a/.cursor/rules/testing/pytest.mdc b/.cursor/rules/testing/pytest.mdc new file mode 100644 index 00000000..1c98d4f3 --- /dev/null +++ b/.cursor/rules/testing/pytest.mdc @@ -0,0 +1,191 @@ +--- +description: Pytest configuration, test structure, test organization, and pytest patterns for Tux +globs: tests/**/*.py, **/test_*.py, **/*_test.py +alwaysApply: false +--- + +# Pytest Testing Framework + +## Overview + +Tux uses pytest with async support, comprehensive plugins, and py-pglite for in-memory PostgreSQL testing. Tests are organized by type (unit, integration, e2e) and use markers for categorization. + +## Test Structure + +### Basic Test Function + +```python +import pytest +from tux.database.service import DatabaseService + +@pytest.mark.unit +async def test_my_function(db_service: DatabaseService) -> None: + """Test description. + + Parameters + ---------- + db_service : DatabaseService + Database service fixture. + """ + # Arrange + expected = "expected_value" + + # Act + result = await my_function() + + # Assert + assert result == expected +``` + +✅ **GOOD:** Proper marker, type hints, docstring, Arrange-Act-Assert pattern + +❌ **BAD:** Missing marker, no type hints, no docstring + +```python +# ❌ BAD: Missing marker and type hints +def test_my_function(db_service): + result = my_function() + assert result == "value" +``` + +### Test Classes + +```python +class TestMyFeature: + """Test suite for MyFeature.""" + + @pytest.mark.unit + async def test_feature_one(self, db_service: DatabaseService) -> None: + """Test feature one.""" + # Test implementation + pass + + @pytest.mark.unit + async def test_feature_two(self, db_service: DatabaseService) -> None: + """Test feature two.""" + # Test implementation + pass +``` + +✅ **GOOD:** Organized in classes, descriptive names + +## Test Organization + +### Directory Structure + +``` +tests/ +├── conftest.py # Pytest configuration and fixtures +├── fixtures/ # Shared fixtures +│ ├── database_fixtures.py +│ ├── test_data_fixtures.py +│ └── sentry_fixtures.py +├── unit/ # Unit tests (fast, isolated) +├── integration/ # Integration tests (database, services) +└── e2e/ # End-to-end tests (full workflows) +``` + +✅ **GOOD:** Clear separation by test type + +### Test File Naming + +```python +# ✅ GOOD: Descriptive test file names +test_database_models.py +test_moderation_service.py +test_permission_system.py + +# ❌ BAD: Generic names +test1.py +tests.py +mytest.py +``` + +## Pytest Configuration + +### Markers + +Markers are defined in `pyproject.toml`: + +```toml +[tool.pytest.ini_options.markers] +unit = "Unit tests (uses py-pglite)" +integration = "Integration tests (uses py-pglite)" +slow = "Slow tests (>5 seconds)" +database = "Tests requiring database access" +async = "Async tests" +``` + +✅ **GOOD:** Always use appropriate markers + +### Async Support + +Pytest-asyncio is configured for async tests: + +```python +# ✅ GOOD: Async test with marker +@pytest.mark.asyncio +@pytest.mark.unit +async def test_async_function(db_service: DatabaseService) -> None: + result = await async_function() + assert result is not None +``` + +❌ **BAD:** Missing asyncio marker for async tests + +## Test Execution + +### Running Tests + +```bash +# ✅ GOOD: Use test script commands +uv run test all # Full test suite with coverage +uv run test quick # Fast run (no coverage) +uv run test html # Generate HTML report + +# Run specific markers +uv run pytest -m unit +uv run pytest -m integration +uv run pytest -m "not slow" +``` + +### Coverage + +Coverage is automatically generated: + +```bash +# Coverage reports: +# - Terminal output (--cov-report=term-missing) +# - XML (--cov-report=xml) +# - JSON (--cov-report=json) +# - LCOV (--cov-report=lcov) +# - HTML (--cov-report=html) +``` + +## Best Practices + +1. **Use markers** - Always mark tests (unit, integration, etc.) +2. **Type hints** - Type all test function parameters +3. **Docstrings** - Document what each test does +4. **Arrange-Act-Assert** - Follow AAA pattern +5. **Descriptive names** - Test names should describe what they test +6. **Isolated tests** - Tests should not depend on each other +7. **Use fixtures** - Use fixtures for setup/teardown + +## Anti-Patterns + +1. ❌ **Missing markers** - Always use appropriate markers +2. ❌ **No type hints** - Always type parameters +3. ❌ **No docstrings** - Document all tests +4. ❌ **Test dependencies** - Tests should be independent +5. ❌ **Hard-coded values** - Use fixtures and constants +6. ❌ **Slow unit tests** - Unit tests should be fast + +## See Also + +- @testing/fixtures.mdc - Fixture patterns +- @testing/markers.mdc - Test marker conventions +- @testing/coverage.mdc - Coverage requirements +- @testing/async.mdc - Async testing patterns +- @AGENTS.md - General coding standards +- @rules.mdc - Complete catalog of all rules and commands diff --git a/.cursor/rules/ui/cv2.mdc b/.cursor/rules/ui/cv2.mdc index 669d1efc..bef75598 100644 --- a/.cursor/rules/ui/cv2.mdc +++ b/.cursor/rules/ui/cv2.mdc @@ -1,5 +1,5 @@ --- -description: Discord.py Components V2 rules +description: Discord.py Components V2 rules, LayoutView patterns, component types, limits, and interaction handling for Tux globs: src/tux/ui/**/*.py alwaysApply: false --- @@ -48,6 +48,29 @@ class MyLayout(ui.LayoutView): ## LayoutView Methods - `walk_children()` - Yields all children including nested + +## Best Practices + +1. **Use LayoutView for new components** - Prefer Components V2 +2. **Define items as class variables** - No manual `add_item` needed +3. **Use ActionRow for buttons/selects** - Required except Section accessory +4. **Respect component limits** - Max 40 components total +5. **Respect character limits** - Max 4000 chars across TextDisplay items +6. **Type interactions** - Always type interaction parameters + +## Anti-Patterns + +1. ❌ **Mixing V1 and V2** - Don't mix old View with LayoutView +2. ❌ **Manual add_item** - Use class variables instead +3. ❌ **Exceeding limits** - Respect component and character limits +4. ❌ **Missing ActionRow** - Buttons/selects must be in ActionRow +5. ❌ **No type hints** - Always type interaction parameters + +## See Also + +- @modules/interactions.mdc - Interaction response patterns +- @modules/commands.mdc - Command patterns +- @AGENTS.md - General coding standards - `find_item(id)` - Find component by numerical ID - `content_length()` - Total characters across all TextDisplay - `total_children_count` - Count including nested children From a73eb6319b52955b2dc6ebaa019e0a3aa0598f24 Mon Sep 17 00:00:00 2001 From: Logan Honeycutt Date: Sun, 7 Dec 2025 01:09:48 -0500 Subject: [PATCH 06/11] feat(templates): add command and rule templates for documentation - Introduced new markdown templates for commands and rules to standardize documentation practices. - The command template includes sections for overview, steps, error handling, and checklists. - The rule template features patterns, best practices, anti-patterns, and examples to guide users in implementation. - These templates aim to enhance clarity and consistency in documenting commands and rules within the Tux project. --- .cursor/templates/command-template.md | 44 +++++++++++++++++++ .cursor/templates/rule-template.mdc | 63 +++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 .cursor/templates/command-template.md create mode 100644 .cursor/templates/rule-template.mdc diff --git a/.cursor/templates/command-template.md b/.cursor/templates/command-template.md new file mode 100644 index 00000000..58be9328 --- /dev/null +++ b/.cursor/templates/command-template.md @@ -0,0 +1,44 @@ +# Command Title + +## Overview + +Brief description of command purpose and expected outcome. Explain what this command does and when to use it. + +## Steps + +1. **Step Title** + - Sub-step or detail + - Additional context or explanation + - Important notes or warnings + +2. **Step Title** + - Sub-step or detail + - More information + +3. **Step Title** + - Sub-step or detail + - Final step details + +## Error Handling + +If operation fails: + +- Troubleshooting step 1 +- Troubleshooting step 2 +- Common error solutions + +## Checklist + +- [ ] Item one - Verifiable action +- [ ] Item two - Another verifiable action +- [ ] Item three - Final verification + +## See Also + +- Related command: `/other-command` - Related workflow +- Related rule: @rule-name.mdc - Related pattern +- Related rule: @another-rule.mdc - Another related pattern + +## Additional Notes + +Any additional context, constraints, or considerations that don't fit in other sections. diff --git a/.cursor/templates/rule-template.mdc b/.cursor/templates/rule-template.mdc new file mode 100644 index 00000000..aa15884e --- /dev/null +++ b/.cursor/templates/rule-template.mdc @@ -0,0 +1,63 @@ +--- +description: Brief description with domain keywords (60-120 chars) +globs: file/pattern/to/match/**/*.*, another/pattern/to/match/**/*.* +alwaysApply: false # true for always-apply rules +--- + +# Rule Title + +## Overview + +Brief description of the rule's purpose and scope. Explain what this rule covers and when it applies. + +## Patterns + +### ✅ GOOD: Correct Pattern + +```python +# Example of correct implementation +# Include working code examples +class MyClass: + """Proper docstring.""" + def method(self) -> None: + # Implementation + pass +``` + +### ❌ BAD: Anti-Pattern + +```python +# Example of what NOT to do +# Show common mistakes +class MyClass: + def method(self): # Missing type hints, docstring + pass +``` + +## Best Practices + +1. **Practice 1** - Explanation of best practice +2. **Practice 2** - Another best practice +3. **Practice 3** - Additional guidance + +## Anti-Patterns + +1. ❌ **Anti-pattern 1** - Why this is bad +2. ❌ **Anti-pattern 2** - Common mistake to avoid +3. ❌ **Anti-pattern 3** - What not to do + +## Examples + +More detailed examples demonstrating the pattern in various contexts. + +```python +# Additional example +# Show different use cases +``` + +## See Also + +- @related-rule.mdc - Related rule +- @another-rule.mdc - Another related rule +- @AGENTS.md - General coding standards +- @.cursor/rules/rules.mdc - Complete catalog of all rules and commands From 5f8370fd3fcbc2e8a0746febf10d18b220727389 Mon Sep 17 00:00:00 2001 From: Logan Honeycutt Date: Sun, 7 Dec 2025 01:11:37 -0500 Subject: [PATCH 07/11] feat(guides): add comprehensive guides for creating Cursor commands and rules - Introduced two new guides: "Creating Cursor Commands" and "Creating Cursor Rules" to provide step-by-step instructions for developers. - Each guide includes quick start sections, detailed steps, best practices, and templates to standardize the creation of commands and rules within the Tux project. - Updated the main developer guides index to include links to the new guides, enhancing accessibility and organization of documentation resources. --- .../guides/creating-cursor-commands.md | 277 ++++++++++++++++++ .../developer/guides/creating-cursor-rules.md | 275 +++++++++++++++++ docs/content/developer/guides/index.md | 10 +- 3 files changed, 559 insertions(+), 3 deletions(-) create mode 100644 docs/content/developer/guides/creating-cursor-commands.md create mode 100644 docs/content/developer/guides/creating-cursor-rules.md diff --git a/docs/content/developer/guides/creating-cursor-commands.md b/docs/content/developer/guides/creating-cursor-commands.md new file mode 100644 index 00000000..d6a94183 --- /dev/null +++ b/docs/content/developer/guides/creating-cursor-commands.md @@ -0,0 +1,277 @@ +--- +title: Creating Cursor Commands +tags: + - cursor + - commands + - development + - guide +--- + +# Guide: Creating Cursor Commands + +Step-by-step guide for creating new Cursor commands for the Tux project. + +## Quick Start + +1. **Choose Category** - Select appropriate category directory +2. **Copy Template** - Use `.cursor/templates/command-template.md` +3. **Fill Content** - Add project-specific workflow +4. **Validate** - Ensure command meets standards +5. **Test** - Verify command works correctly + +## Detailed Steps + +### Step 1: Choose Category + +Select the category that best fits your command: + +- **`code-quality/`** - Code quality workflows +- **`testing/`** - Testing workflows +- **`database/`** - Database workflows +- **`discord/`** - Discord bot workflows +- **`security/`** - Security workflows +- **`debugging/`** - Debugging workflows +- **`error-handling/`** - Error handling workflows +- **`documentation/`** - Documentation workflows +- **`development/`** - Development workflows + +### Step 2: Create File + +1. Navigate to appropriate category directory +2. Create new `.md` file with kebab-case name +3. Example: `database/migration.md` + +### Step 3: Write Title + +Use clear, action-oriented title: + +```markdown +# Database Migration +``` + +### Step 4: Write Overview + +Brief description of what the command does: + +```markdown +## Overview + +Create and apply Alembic database migrations for schema changes. +``` + +### Step 5: Write Steps + +Numbered steps with sub-steps: + +```markdown +## Steps + +1. **Create Migration** + - Run `uv run db migrate-dev "description"` + - Review generated migration file + - Verify upgrade and downgrade functions + +2. **Review Migration** + - Ensure both functions implemented + - Check for proper constraints + - Validate enum handling +``` + +### Step 6: Add Error Handling + +Include troubleshooting: + +```markdown +## Error Handling + +If migration fails: +- Check migration file syntax +- Verify database connection +- Review error messages +``` + +### Step 7: Add Checklist + +Verifiable checklist items: + +```markdown +## Checklist + +- [ ] Migration file created +- [ ] Both upgrade and downgrade implemented +- [ ] Migration tested +- [ ] Applied to development database +``` + +### Step 8: Add Cross-References + +Link to related commands and rules: + +```markdown +## See Also + +- Related command: `/database-health` +- Related rule: @database/migrations.mdc +``` + +## Example: Creating a Database Command + +1. **Category**: `database/` +2. **File**: `database/migration.md` +3. **Title**: `# Database Migration` +4. **Overview**: Brief description +5. **Steps**: Numbered workflow steps +6. **Error Handling**: Troubleshooting guide +7. **Checklist**: Verifiable items +8. **See Also**: Cross-references + +## Common Patterns + +### Simple Command + +```markdown +# Command Title + +## Overview +Brief description. + +## Steps +1. Step one +2. Step two + +## Checklist +- [ ] Item one +- [ ] Item two +``` + +### Complex Command + +```markdown +# Command Title + +## Overview +Detailed description. + +## Steps +1. **Step Title** + - Sub-step + - Detail +2. **Step Title** + - Sub-step + +## Error Handling +Troubleshooting steps. + +## Checklist +- [ ] Item one +- [ ] Item two + +## See Also +- Related command: `/other` +- Related rule: @rule.mdc +``` + +## Best Practices + +1. **Be Action-Oriented** - Use verbs in title +2. **Clear Steps** - Numbered, with sub-steps +3. **Complete Checklist** - Verifiable items +4. **Error Handling** - Include troubleshooting +5. **Cross-Reference** - Link to related commands/rules +6. **Project-Specific** - Reference Tux tools and patterns + +## Templates + +### Complete Command Template + +```markdown +# Command Title + +## Overview + +Brief description of command purpose and expected outcome. + +## Steps + +1. **Step Title** + - Sub-step or detail + - Additional context +2. **Step Title** + - Sub-step or detail + +## Error Handling + +If operation fails: +- Troubleshooting step 1 +- Troubleshooting step 2 + +## Checklist + +- [ ] Item one +- [ ] Item two +- [ ] Item three + +## See Also + +- Related command: `/other-command` +- Related rule: @rule-name.mdc +``` + +## Standards + +### Naming + +- **Commands**: kebab-case, action-oriented (e.g., `database-migration.md`) +- **Location**: `.cursor/commands/{category}/` +- **Format**: `.md` files (plain markdown) + +### Content Requirements + +- **Project-specific**: Tailored to Tux, not generic +- **Examples**: Include working code examples +- **Error handling**: Show how to handle failures +- **Cross-references**: Link to related commands/rules + +### Quality Standards + +- Clear, actionable content +- Consistent formatting +- Complete checklists +- Proper error handling + +## Validation + +Run validation before committing: + +```bash +uv run rules validate +``` + +This checks: + +- File structure +- Content requirements (steps, checklist) +- Error handling included +- Cross-references complete + +## Maintenance + +### Updating Commands + +- Update steps when workflows change +- Keep checklists current +- Update error handling as needed +- Verify all commands still work + +### Review Process + +- Review commands during code reviews +- Update when project patterns change +- Remove obsolete commands +- Consolidate duplicate content + +## See Also + +- [Command Template](../../../../.cursor/templates/command-template.md) +- [Creating Cursor Rules](creating-cursor-rules.md) - Guide for creating rules +- @meta/cursor-commands.mdc - Commands specification diff --git a/docs/content/developer/guides/creating-cursor-rules.md b/docs/content/developer/guides/creating-cursor-rules.md new file mode 100644 index 00000000..bc8dc2d5 --- /dev/null +++ b/docs/content/developer/guides/creating-cursor-rules.md @@ -0,0 +1,275 @@ +--- +title: Creating Cursor Rules +tags: + - cursor + - rules + - development + - guide +--- + + + +# Guide: Creating Cursor Rules + +Step-by-step guide for creating new Cursor rules for the Tux project. + +## Quick Start + +1. **Choose Domain** - Select appropriate domain directory +2. **Copy Template** - Use `.cursor/templates/rule-template.mdc` +3. **Fill Content** - Add project-specific patterns +4. **Validate** - Ensure file meets standards +5. **Test** - Verify rule applies correctly + +## Detailed Steps + +### Step 1: Choose Domain + +Select the domain that best fits your rule: + +- **`core/`** - Core project rules (tech stack, dependencies) +- **`database/`** - Database patterns (models, migrations, controllers) +- **`modules/`** - Discord bot modules (cogs, commands, events) +- **`testing/`** - Testing patterns (pytest, fixtures, markers) +- **`docs/`** - Documentation rules (Zensical, writing standards) +- **`security/`** - Security patterns (secrets, validation) +- **`error-handling/`** - Error handling (logging, Sentry) +- **`ui/`** - UI components (Discord Components V2) +- **`meta/`** - System documentation (specifications) + +### Step 2: Create File + +1. Navigate to appropriate domain directory +2. Create new `.mdc` file with kebab-case name +3. Example: `database/models.mdc` + +### Step 3: Add Metadata + +Copy and customize frontmatter: + +```yaml +--- +description: Brief description (60-120 chars) with domain keywords +globs: src/tux/database/**/*.py # Optional, comma-separated for file-scoped +alwaysApply: false # true for always-apply rules +--- +``` + +**Metadata Guidelines:** + +- **description**: Include domain keywords, be specific +- **globs**: Use comma-separated file patterns for file-scoped rules (no quotes, no brackets) +- **alwaysApply**: Only true for project-wide standards + +### Step 4: Write Content + +Follow this structure: + +1. **Title (H1)** - Clear, descriptive +2. **Overview** - Purpose and scope +3. **Patterns** - ✅ GOOD / ❌ BAD examples +4. **Best Practices** - Key guidelines +5. **Anti-Patterns** - What to avoid +6. **Examples** - Detailed examples (optional) +7. **See Also** - Cross-references + +### Step 5: Add Examples + +Include working code examples: + +```python +# ✅ GOOD: Show correct pattern +from tux.database.models import BaseModel + +class MyModel(BaseModel, table=True): + """Model description.""" + id: int = Field(primary_key=True) +``` + +```python +# ❌ BAD: Show anti-pattern +class MyModel: # Missing BaseModel + id: int # Missing Field definition +``` + +### Step 6: Add Cross-References + +Link to related rules: + +```markdown +## See Also + +- @database/migrations.mdc - Migration patterns +- @database/controllers.mdc - Controller patterns +- @AGENTS.md - General coding standards +``` + +### Step 7: Validate + +Check: + +- [ ] File under 500 lines +- [ ] Includes code examples +- [ ] Includes anti-patterns +- [ ] Has proper metadata +- [ ] Cross-references complete +- [ ] Project-specific (not generic) + +## Example: Creating a Database Rule + +1. **Domain**: `database/` +2. **File**: `database/models.mdc` +3. **Metadata**: + + ```yaml + description: SQLModel database model patterns for Tux + globs: src/tux/database/models/**/*.py + alwaysApply: false + ``` + +4. **Content**: Add patterns, examples, anti-patterns +5. **Cross-references**: Link to migrations, controllers rules + +## Common Patterns + +### File-Scoped Rule + +```yaml +--- +description: Patterns for specific file type +globs: src/tux/modules/**/*.py +alwaysApply: false +--- +``` + +### Always-Apply Rule + +```yaml +--- +description: Project-wide standard +alwaysApply: true +--- +``` + +### Intelligent Rule + +```yaml +--- +description: Domain-specific patterns with keywords +alwaysApply: false +--- +``` + +## Best Practices + +1. **Be Specific** - Tailor to Tux project, not generic +2. **Include Examples** - Show working code +3. **Show Anti-Patterns** - What NOT to do +4. **Cross-Reference** - Link to related rules +5. **Keep Focused** - One domain/concern per rule +6. **Stay Current** - Update when patterns change + +## Templates + +### Complete Rule Template + +```markdown +--- +description: Brief description (60-120 chars) with domain keywords +globs: optional/file/pattern, another/pattern +alwaysApply: false +--- + +# Rule Title + +## Overview + +Brief description of the rule's purpose and scope. + +## Patterns + +✅ **GOOD:** Example of correct pattern +```python +# Code example +``` + +❌ **BAD:** Example of anti-pattern + +```python +# Code example +``` + +## Best Practices + +1. Practice 1 +2. Practice 2 + +## Anti-Patterns + +1. ❌ Anti-pattern 1 +2. ❌ Anti-pattern 2 + +## See Also + +- @related-rule.mdc +- @AGENTS.md + +## Standards + +### Naming + +- **Rules**: kebab-case, descriptive (e.g., `database-models.mdc`) +- **Location**: `.cursor/rules/{domain}/` +- **Format**: `.mdc` files with YAML frontmatter + +### Content Requirements + +- **Project-specific**: Tailored to Tux, not generic +- **Examples**: Include working code examples +- **Anti-patterns**: Show what NOT to do +- **Cross-references**: Link to related rules/commands + +### Quality Standards + +- Clear, actionable content +- Consistent formatting +- File under 500 lines +- Proper metadata format + +## Validation + +Run validation before committing: + +```bash +uv run rules validate +``` + +This checks: + +- Frontmatter format +- Description length (60-120 chars) +- Content requirements (examples, anti-patterns) +- Globs format (comma-separated, no quotes/brackets) +- File structure + +## Maintenance + +### Updating Rules + +- Keep content current with code changes +- Update examples when patterns change +- Add new anti-patterns as discovered +- Update cross-references when structure changes + +### Review Process + +- Review rules during code reviews +- Update when project patterns change +- Remove obsolete rules +- Consolidate duplicate content + +## See Also + +- [Rule Template](../../../../.cursor/templates/rule-template.mdc) +- [Creating Cursor Commands](creating-cursor-commands.md) - Guide for creating commands +- @meta/cursor-rules.mdc - Rules specification diff --git a/docs/content/developer/guides/index.md b/docs/content/developer/guides/index.md index cb90eea4..84056df3 100644 --- a/docs/content/developer/guides/index.md +++ b/docs/content/developer/guides/index.md @@ -7,9 +7,13 @@ tags: # Developer Guides -!!! warning "Work in progress" - This section is a work in progress. Please help us by contributing to the documentation. - This section contains guides for developers working with Tux. +## Available Guides + +- **[Components V2](components-v2.md)** - Using Discord.py Components V2 +- **[Extending CLI](extending-cli.md)** - Adding new CLI commands +- **[Creating Cursor Rules](creating-cursor-rules.md)** - Guide for creating Cursor rules +- **[Creating Cursor Commands](creating-cursor-commands.md)** - Guide for creating Cursor commands + From 07ad10e3a53f73536f0d34a1b80a20cbd4f51fe2 Mon Sep 17 00:00:00 2001 From: Logan Honeycutt Date: Sun, 7 Dec 2025 01:11:43 -0500 Subject: [PATCH 08/11] feat(cursor): add README for Cursor rules and commands - Introduced a new README file detailing the structure and usage of Cursor's rules and commands system. - Provided an overview of rules and commands, their organization by domain and category, and instructions for usage. - Included links to comprehensive guides and resources for contributing to Cursor rules and commands, enhancing developer onboarding and documentation accessibility. --- .cursor/README.md | 75 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 .cursor/README.md diff --git a/.cursor/README.md b/.cursor/README.md new file mode 100644 index 00000000..6fd9f490 --- /dev/null +++ b/.cursor/README.md @@ -0,0 +1,75 @@ +# Cursor Rules & Commands + +The Tux project uses Cursor's rules and commands system to provide AI-assisted development with project-specific patterns and workflows. + +## Overview + +This directory contains: + +- **Rules** (`.mdc` files) - Project-specific coding patterns and standards +- **Commands** (`.md` files) - Reusable workflows and task automation + +Rules are automatically applied by Cursor based on file patterns, while commands are invoked manually with the `/` prefix in Cursor chat. + +## Structure + +### Rules + +Rules are organized by domain in `.cursor/rules/`: + +- **`core/`** - Core project rules (tech stack, dependencies) +- **`database/`** - Database layer patterns (models, migrations, controllers, services, queries) +- **`modules/`** - Discord bot modules (cogs, commands, events, permissions, interactions) +- **`testing/`** - Testing patterns (pytest, fixtures, markers, coverage, async) +- **`docs/`** - Documentation rules (Zensical, writing standards, structure) +- **`security/`** - Security patterns (secrets, validation, dependencies) +- **`error-handling/`** - Error handling (patterns, logging, Sentry, user feedback) +- **`ui/`** - UI components (Discord Components V2) +- **`meta/`** - System documentation (Cursor rules/commands specifications) + +### Commands + +Commands are organized by category in `.cursor/commands/`: + +- **`code-quality/`** - Code quality workflows (lint, refactor, review) +- **`testing/`** - Testing workflows (run tests, coverage, integration) +- **`database/`** - Database workflows (migration, health, reset) +- **`discord/`** - Discord bot workflows (create module, test command, sync) +- **`security/`** - Security workflows (security review) +- **`debugging/`** - Debugging workflows (debug issues) +- **`error-handling/`** - Error handling workflows (add error handling) +- **`documentation/`** - Documentation workflows (generate, update, serve) +- **`development/`** - Development workflows (setup, docker) + +## Usage + +Rules are automatically applied by Cursor: + +- **Always Apply** - Rules with `alwaysApply: true` are active in every chat +- **File-Scoped** - Rules with `globs` patterns apply when editing matching files +- **Intelligent** - Rules with `description` are selected by Cursor based on context + +Commands are invoked manually: + +1. Type `/` in Cursor chat +2. Select command from autocomplete list +3. Command executes with current context + +Example: `/lint` runs the linting workflow + +## Quick Reference + +See [rules/rules.mdc](rules/rules.mdc) for a complete catalog of all rules and commands. + +## Contributing + +See the developer documentation for comprehensive guides on creating and maintaining rules/commands: + +- [Creating Cursor Rules Guide](../docs/content/developer/guides/creating-cursor-rules.md) +- [Creating Cursor Commands Guide](../docs/content/developer/guides/creating-cursor-commands.md) + +## Resources + +- [Cursor Rules Documentation](https://cursor.com/docs/context/rules) +- [Cursor Commands Documentation](https://cursor.com/docs/agent/chat/commands) +- [AGENTS.md](../AGENTS.md) From 99082fd94c353efaec87e40d1393fc451d6ead44 Mon Sep 17 00:00:00 2001 From: Logan Honeycutt Date: Sun, 7 Dec 2025 01:11:54 -0500 Subject: [PATCH 09/11] feat(docs): enhance AGENTS.md with Cursor rules and commands overview - Added a new section detailing Cursor's rules and commands system, including their organization and usage. - Included validation commands and links to comprehensive guides for creating rules and commands. - Updated development workflow to incorporate rules validation as a step before committing changes. - Improved documentation structure for better clarity and accessibility of Cursor-related resources. --- AGENTS.md | 50 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 44040076..0df9c92c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,6 +7,30 @@ **Core:** Python 3.13+ • discord.py • PostgreSQL • SQLModel • Docker **Tools:** uv • ruff • basedpyright • pytest • loguru • sentry-sdk • httpx • Zensical +## Cursor Rules & Commands + +The project uses Cursor's rules and commands system for AI-assisted development: + +See [.cursor/rules/rules.mdc](.cursor/rules/rules.mdc) for the complete catalog of all rules and commands. + +- **Rules** (`.cursor/rules/*.mdc`) - Project-specific coding patterns automatically applied +- **Commands** (`.cursor/commands/*.md`) - Reusable workflows invoked with `/` prefix + +**Validation:** + +```bash +uv run rules validate # Validate all rules and commands +``` + +**Documentation:** + +- [Creating Cursor Rules](docs/content/developer/guides/creating-cursor-rules.md) +- [Creating Cursor Commands](docs/content/developer/guides/creating-cursor-commands.md) +- [Cursor Rules & Commands Overview](.cursor/README.md) + +!!! tip "Detailed Rules Available" + This file provides a high-level overview. For detailed, domain-specific patterns and standards, see the rules in `.cursor/rules/` organized by domain (database, modules, testing, security, etc.). + ## Quick Setup ```bash @@ -33,7 +57,11 @@ tux/ ├── tests/ # Tests (unit/integration/e2e) ├── docs/ # Zensical documentation ├── docker/ # Docker related files -└── config/ # Config examples +├── config/ # Config examples +└── .cursor/ # Cursor rules & commands + ├── rules/ # AI coding patterns (.mdc) + ├── commands/ # Workflow commands (.md) + └── templates/ # Rule/command templates ``` ## Code Standards @@ -50,6 +78,11 @@ tux/ **Quality checks:** +```bash +uv run dev all # Run all quality checks (lint, type, format) +uv run dev pre-commit # Run pre-commit checks +``` + ## Testing ```bash @@ -62,7 +95,7 @@ uv run test html # Generate HTML report ## Database -**Stack:** SQLModel (ORM) • Alembic (migrations) • PostgreSQL (asyncpg) +**Stack:** SQLModel (ORM) • Alembic (migrations) • PostgreSQL (psycopg async) ```bash uv run db init # Initialize with migrations @@ -106,12 +139,19 @@ uv run config generate # Generate configuration example files uv run config validate # Validate the current configuration ``` +**Cursor:** + +```bash +uv run rules validate # Validate Cursor rules and commands +``` + ## Development Workflow 1. **Setup:** `uv sync` → configure `.env` & `config.toml` 2. **Develop:** Make changes → `uv run dev all` → `uv run test quick` 3. **Database:** Modify models → `uv run db new "description"` → `uv run db dev` -4. **Commit:** `uv run dev pre-commit` → `uv run test all` +4. **Rules:** Validate rules/commands → `uv run rules validate` +5. **Commit:** `uv run dev pre-commit` → `uv run test all` ## Conventional Commits @@ -144,6 +184,7 @@ refactor(database): optimize query performance - All tests pass (`uv run test all`) - Quality checks pass (`uv run dev all`) - Migrations tested (`uv run db dev`) +- Cursor rules/commands validated (`uv run rules validate`) - Documentation updated - Type hints complete - Docstrings for public APIs @@ -216,6 +257,9 @@ uv run basedpyright --verbose # Test failures uv run pytest -v -s + +# Cursor validation +uv run rules validate ``` ## Resources From 6a0c92d6768dcea7398ebedf9d1c66ebe682f887 Mon Sep 17 00:00:00 2001 From: Logan Honeycutt Date: Sun, 7 Dec 2025 01:12:01 -0500 Subject: [PATCH 10/11] feat(docs): add new guides for creating Cursor rules and commands - Included links to "Creating Cursor Rules" and "Creating Cursor Commands" in the developer guides index. - These additions enhance the documentation structure and provide developers with essential resources for implementing Cursor functionalities. --- zensical.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zensical.toml b/zensical.toml index 7837118b..a9a631eb 100644 --- a/zensical.toml +++ b/zensical.toml @@ -90,6 +90,8 @@ nav = [ "developer/guides/index.md", "developer/guides/components-v2.md", "developer/guides/extending-cli.md", + "developer/guides/creating-cursor-rules.md", + "developer/guides/creating-cursor-commands.md", ] }, { "Concepts" = [ "developer/concepts/index.md", From 1f73363ef60e4b455589cfc8401efcc2441fd9cf Mon Sep 17 00:00:00 2001 From: Logan Honeycutt Date: Sun, 7 Dec 2025 01:17:31 -0500 Subject: [PATCH 11/11] chore(pre-commit): update configuration to enhance commit message handling - Added autofix and autoupdate commit message formats for pre-commit hooks. - Excluded validation rules from the pre-commit checks to streamline the process. - Updated Python version specification to 3.13 for consistency. --- .pre-commit-config.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 96890de6..1b9a3c65 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,4 +1,8 @@ --- +ci: + autofix_commit_msg: 'style: auto fixes from pre-commit hooks' + autoupdate_commit_msg: 'chore: update pre-commit hook versions' + skip: [validate-rules] default_language_version: python: python3.13 repos: @@ -89,6 +93,3 @@ repos: files: \.cursor/(rules|commands)/.*(\.mdc|\.md)$ pass_filenames: false exclude: ^(\.archive/|.*typings/|node_modules/|\.venv/|\.kiro/|src/tux/database/migrations/versions/).*$ -ci: - autofix_commit_msg: 'style: auto fixes from pre-commit hooks' - autoupdate_commit_msg: 'chore: update pre-commit hook versions'