# AI-Optimized Python Style Guide This document defines the Python style conventions for the Manual Slop codebase. These deviate from PEP 8 / Google style to minimize token consumption when code is processed by AI agents, while preserving readability for human review. ## 1. Indentation and Whitespace - **Indentation:** 1 space per level. No tabs. - **Continuation lines:** 1 space relative to the opening construct. - **Blank lines:** Zero blank lines between function/method definitions within a class. One blank line between top-level definitions only when separating logically distinct sections. - **Trailing whitespace:** None. - **Rationale:** 1-space indentation reduces token count by ~40% compared to 4-space on deeply nested GUI code, with no loss of structural clarity for AST-based tools. ### Maximum Nesting Depth - **Hard limit: 5 levels maximum.** - AI agents consistently misinterpret Python scope via indentation. This hard limit prevents deeply nested blocks that confuse model interpretation. - Classes and async handlers typically consume 1-2 levels before business logic begins, so 5 accommodates real-world usage patterns. - **Enforcement:** Use ruff `[tool.ruff.lint.mccabe] max-complexity = 5` in the project linter config. - **Refactoring mandate:** Any block exceeding 5 levels must be extracted into a named function. Do not "work around" this with tricks—extract the logic. - **Rationale:** GUI callbacks and async handlers naturally want to nest. This constraint forces extraction of deep logic into testable, named units. ## 2. Type Annotations - **All functions and methods** must have return type annotations. - **All parameters** (except `self`/`cls`) must have type annotations. - **Module-level and class-level variables** must have type annotations. - **Use modern syntax:** `list[str]`, `dict[str, Any]`, `X | None` over `Optional[X]` where Python 3.10+ is available. Use `from __future__ import annotations` if needed. - **Callable:** Use bare `Callable` for callback factories. Use `Callable[[ArgTypes], ReturnType]` when the signature is known and stable. - **DearPyGui / ImGui callbacks:** Use `sender: Any, app_data: Any` for framework callbacks where the types are runtime-determined. ## 3. Imports - Use `from __future__ import annotations` at the top of every module. - Group imports: stdlib, third-party, local — separated by a blank line. - Use `from typing import Any, Optional, Callable` etc. for type-only imports. - Prefer `from x import Y` for specific symbols over `import x` when only one or two names are used. ## 4. Naming - **snake_case** for modules, functions, methods, variables. - **PascalCase** for classes. - **ALL_CAPS** for module-level constants. - **Single leading underscore** (`_name`) for internal/private members. ## 5. Docstrings - Required on classes and non-trivial public functions. - Use `"""triple double quotes"""`. - One-line summary is sufficient for simple methods. - Omit docstrings on obvious internal methods (e.g., `_cb_*` callbacks, `_render_*` UI methods) where the name is self-documenting. ## 6. String Formatting - Prefer f-strings. - Use double quotes (`"`) for strings by default. - Use single quotes when the string contains double quotes. ## 7. Error Handling - Never use bare `except:`. - Use specific exception types. - Prefer `if x is None:` over `if not x:` when testing for None specifically. ## 8. AI-Agent Specific Conventions - **No redundant comments.** Do not add comments that restate what the code does. Only comment on *why* when non-obvious. - **No empty `__init__.py` files.** - **Minimal blank lines.** Token-efficient density is preferred over visual padding. - **Short variable names are acceptable** in tight scopes (loop vars, lambdas). Use descriptive names for module-level and class attributes. ## 10. Anti-OOP Conventions ### Philosophy AI agents consistently misinterpret class hierarchies, method resolution, and inheritance. Flat function-call graphs are deterministic and traceable. OOP introduces scoping complexity that compounds with indentation. ### Hard Rules (Enforced by lint) - **Never write a class for a single method.** Use a function. - **Never use inheritance for code reuse.** Compose with standalone functions. - **Never use private methods (`_method`).** Module-level functions with clear names suffice. - **No nested classes.** Define helper types at module level. - **No decorator classes.** Use plain functions with decorators. ### Class Justification Required Every class definition MUST include a comment explaining WHY it is a class and not a function group or struct: ```python # JUSTIFIED: Holds mutable shared state across multiple async operations # Cannot be replaced by functions without passing state through every call class AsyncOperationScheduler: ... # NOT JUSTIFIED: Simply groups related functions # Should be module-level functions in operations.py class OperationHelper: def validate(self): ... def execute(self): ... ``` ### Acceptability Criteria A class is justified ONLY when ALL of: 1. It holds mutable state that must be encapsulated 2. It has 3+ related methods that share state 3. It implements a behavioral interface used polymorphically (not just data grouping) ### Refactoring Existing Classes (Strangler Fig Pattern) When refactoring a class to functions: 1. Write test validating current behavior (prevents regression) 2. Extract one method at a time into module-level functions 3. Create wrapper function that delegates to class until migration complete 4. Delete class only when ALL callers migrated 5. Commit with `refactor(oop):` prefix ### Data Structures - **Data-only containers:** Use `NamedTuple`, `dataclass(frozen=True)`, or plain `dict` — NOT classes - **State machines:** Use dict-based transitions, not class + inheritance - **Configuration:** Plain dict or `TypedDict`, not classes with defaults ### Anti-Patterns (Flagged by Ruff PLR rules) - `PLR0912`: Too many branches — extract to functions - `PLR6301`: No public methods — class is a namespace anti-pattern - `PLR0206`: Descriptors in class body — use simple attributes ### Enforcement ```toml [tool.ruff.lint.select] select = ["E", "F", "W", "C90", "C4", "PLR0912", "PLR6301", "PLR0206"] [tool.ruff.lint.plr] max-returns = 4 max-locals = 8 max-args = 5 ``` ## 11. Structural Dependency Mapping (SDM) To assist AI agents in evaluating refactoring impact across dynamic codebases, all major definitions SHOULD include terse SDM tags at the end of their docstrings. - **Format:** Tags are enclosed in square brackets at the end of the docstring body. - **For Functions/Methods:** `[C: CallerA, CallerB]` — List of primary internal callers within the codebase. - **For State Variables:** - `[M: File:Line, Method]` — List of primary mutation points (where the value is assigned). - `[U: File]` — Major codepaths of use (where the value is read but not changed). ### Example: ```python def start_services(self) -> None: """ Initialises background threads and MCP servers. [C: App.run, _cb_load_project] """ ... ```