Files
manual_slop/conductor/code_styleguides/python.md
T
ed b9c1b63f8d feat(style): Add anti-OOP conventions and OOP refactoring tracker
- Add section 10 (Anti-OOP Conventions) to python.md with hard rules,
  class justification requirements, and Strangler Fig refactoring pattern
- Create conductor/refactor_oop.md tracker with 4 phases for class elimination
- Add ruff PLR rules (PLR0912, PLR6301, PLR0206) to pyproject.toml for
  OOP anti-patterns

Addresses AI agent scope misinterpretation issues by enforcing flat
function-call graphs over deep class hierarchies.
2026-05-11 23:41:41 -04:00

7.0 KiB

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:

# 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

[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:

def start_services(self) -> None:
 """
 Initialises background threads and MCP servers.
 [C: App.run, _cb_load_project]
 """
 ...