SOLID Enforcer (Generation + Review + Refactor)
Purpose
Ensure produced or modified code adheres to SOLID, with designs that are extensible, testable, and maintainable. This skill is used both to:
-
Generate new code that is SOLID by construction, and
-
Refactor/review existing code to become more SOLID without breaking behavior.
When to activate
Activate when the task includes any of:
-
refactor / redesign / architecture changes
-
“make it SOLID”, “clean up responsibilities”, “reduce coupling”
-
adding extension points / plugin systems / strategies / policies
-
replacing large conditionals or type switches
-
improving testability, DI, interface boundaries, modularity
-
PR review focused on design/maintainability
Operating mode (must follow)
-
Clarify (max 1–3 questions) if requirements or constraints are ambiguous (behavioral invariants, extension expectations, performance constraints, public API stability).
-
Plan first for non-trivial changes:
-
identify files/modules to touch
-
state invariants and “must not change” behaviors
-
propose refactor steps with checkpoints (tests/typing/lint)
-
Implement in small, verifiable steps:
-
keep diffs tight
-
preserve stable logic
-
add/adjust tests as proof
-
Prove correctness:
-
add unit tests demonstrating contracts
-
run/describe the minimal commands to validate (tests, typecheck, lint)
-
Return complete output:
-
runnable files (or clear patches)
-
tests
-
short SOLID rationale (how SRP/OCP/LSP/ISP/DIP are satisfied)
SOLID baseline constraints (always enforce)
-
Prefer composition over inheritance unless full substitutability is guaranteed.
-
Keep classes/modules small; if a unit grows rapidly, challenge responsibilities.
-
Make dependencies explicit (constructor/params). Avoid new in business logic; use DI/factories.
-
Return complete, runnable files and tests proving the contract.
Principle checklists
SRP — Single Responsibility
Pass if:
-
Each class/module has one reason to change.
-
Logging/validation/error-handling are separated from domain logic.
-
Methods do one thing; if a description needs “and/or”, split it.
Common fixes:
-
Extract validation into validators/policies
-
Extract I/O (HTTP/DB/files) into adapters/clients
-
Split orchestration (use-case/service) from pure domain objects
OCP — Open/Closed
Pass if:
-
New behaviors can be added by adding new implementations, not editing stable logic.
-
Replace type-based switches with polymorphism (strategy/decorator/handlers).
Common fixes:
-
Strategy pattern: Policy / Algorithm interface + implementations
-
Decorator: wrap behavior without editing core
-
Registration/dispatch tables (map keys -> handlers) where appropriate
LSP — Liskov Substitution
Pass if:
-
Subtypes preserve invariants.
-
No tighter preconditions / no looser postconditions.
-
No “noop/throw overrides” that violate expectations.
Common fixes:
-
Split base interface into smaller role interfaces (often pairs with ISP)
-
Prefer composition if inheritance requires exceptions
-
Add contract tests for substitutability (same test suite for all impls)
ISP — Interface Segregation
Pass if:
-
Interfaces are minimal and client-specific.
-
“Fat” interfaces are split; clients depend only on what they use.
Common fixes:
-
Break one large interface into role interfaces
-
Compose roles into larger façades only where needed
DIP — Dependency Inversion
Pass if:
-
High-level modules depend on abstractions, not concretions.
-
Interfaces are defined near the clients (ports), implementations elsewhere (adapters).
-
Construction is pushed to the composition root; business logic receives dependencies.
Common fixes:
-
Constructor/parameter injection
-
Factories/providers passed in (not created inside domain logic)
-
In TS: avoid importing concrete implementations into core domain/use-cases
-
In Python: avoid instantiating clients inside use-case functions; inject them
Refactor playbook (preferred approach)
-
Identify responsibilities and seams:
-
what is domain logic vs infrastructure vs orchestration?
-
Introduce abstractions only where they enable extension/testability:
-
define small interfaces (ports) at the boundary
-
Replace conditionals with strategies/handlers:
-
keep stable logic unchanged; move variability behind an interface
-
Push object creation outward:
-
create a composition root (factory/module) for wiring
-
Lock behavior with tests before/after:
-
contract tests for interfaces
-
regression tests for prior behavior
Output contract (what to return)
Always return:
-
Code + unit tests that demonstrate the contract.
-
A short rationale explicitly answering:
-
SRP: what responsibilities were separated?
-
OCP: what is now extensible without edits to stable logic?
-
LSP: how substitutability is preserved (or why inheritance was avoided)?
-
ISP: how interfaces were slimmed/split?
-
DIP: what dependencies are injected and where is wiring done?
-
If relevant: a minimal example showing how to extend via a new implementation.
Red flags (must call out and fix)
-
Business logic instantiates concretes (new , direct client creation) rather than receiving deps.
-
Large modules/classes growing in responsibilities.
-
Type switches/if-chains driving behavior selection across the codebase.
-
Inheritance hierarchies with special-case overrides or exceptions.