/sc:principles - Code Principles Validator
Mandatory validation skill enforcing four fundamental software engineering principles:
-
KISS (Keep It Simple, Stupid) - Code should be as simple as possible
-
Functional Core, Imperative Shell - Business logic must be pure; I/O belongs at edges
-
SOLID - Single Responsibility, Open-Closed, Liskov Substitution, Interface Segregation, Dependency Inversion
-
Let It Crash - Fail fast for bugs; handle errors explicitly at boundaries
Quick Start
Full validation (all four principles)
/sc:principles src/
KISS only validation
/sc:principles src/ --kiss-only
Purity only validation
/sc:principles src/ --purity-only
SOLID only validation
/sc:principles src/ --solid-only
Let It Crash only validation
/sc:principles src/ --crash-only
Strict mode (warnings become errors)
/sc:principles src/ --strict
Generate detailed report
/sc:principles src/ --report
Behavioral Flow
-
INITIALIZE ├── Detect scope root ├── Find changed Python files (git diff) └── Determine file contexts (core vs shell)
-
ANALYZE ├── Parse AST for each file ├── Calculate complexity metrics (KISS) ├── Detect I/O patterns (Purity) ├── Check structural patterns (SOLID) └── Analyze error handling (Let It Crash)
-
VALIDATE ├── Compare against thresholds ├── Classify violations (error vs warning) └── Apply context rules (core stricter than shell)
-
REPORT/BLOCK ├── Output violations with locations ├── Generate actionable recommendations └── Exit 0 (pass) or 2 (blocked)
Validation Rules
KISS Gate
Metric Threshold Severity Description
Cyclomatic Complexity
10 error Number of independent paths through code
Cyclomatic Complexity
7 warning Early warning for growing complexity
Cognitive Complexity
15 error Weighted complexity (nested structures count more)
Function Length
50 lines error Lines of code per function (inclusive count)
Nesting Depth
4 levels error If/for/while/with/try nesting
Parameter Count
5 warning Function parameters
Cognitive vs Cyclomatic Complexity:
-
Cyclomatic counts decision points (branches)
-
Cognitive weights nested structures more heavily: 1 + nesting_depth per control structure
-
Example: if (if (if ...)) has low cyclomatic but high cognitive (hard to read)
Purity Gate
The "Functional Core, Imperative Shell" pattern:
Layer Path Patterns I/O Allowed Severity
Core /domain/ , /logic/ , /services/ , /utils/ , /core/
NO error
Shell /handlers/ , /adapters/ , /api/ , /cli/ , /scripts/ , /tests/
YES warning
Note: Files in /archive/ , /examples/ , /benchmarks/ , conftest.py , setup.py , and *main.py are treated as shell (I/O allowed).
Detected I/O Patterns:
Category Examples
File I/O open() , read() , write() , Path.read_text()
Network requests.get() , httpx , urllib , socket
Database execute() , query() , session.add() , cursor
Subprocess subprocess.run() , os.system() , Popen
Global State global , nonlocal keywords
Side Effects print() , logging.* , logger.*
Async I/O async def , await , async for , async with
SOLID Gate
Detects structural violations of SOLID design principles.
Principle Detection Severity Description
SRP File >300 lines, class with >5 public methods warning Single Responsibility - one reason to change
OCP if/elif chains on type, isinstance cascades warning Open-Closed - extend, don't modify
LSP Override that raises NotImplementedError
error Liskov Substitution - honor contracts
ISP Interface/protocol with >7 methods warning Interface Segregation - small interfaces
DIP Direct instantiation in business logic warning Dependency Inversion - depend on abstractions
Code Smells to Flag:
Smell Principle Recommended Fix
File >300 lines SRP Extract responsibilities into modules
if/else type chains OCP Strategy pattern or registry
Override that throws LSP Honor base contract or don't inherit
10+ method interface ISP Split into focused interfaces
new Service() in logic DIP Dependency injection
Let It Crash Gate
Detects anti-patterns in error handling based on the "Let It Crash" philosophy.
Pattern Severity Description
Bare except:
error Catches all exceptions including KeyboardInterrupt
except Exception: (no re-raise) warning Swallows errors without handling
except: pass
error Silent failure, debugging nightmare
Nested try/except fallbacks warning Complex error paths, hard to debug
When to Let It Crash:
-
Validation failures → crash with clear error
-
Programming errors → surface immediately
-
Internal operations → let them fail
When to Handle Errors (exceptions to rule):
-
Data persistence → protect against data loss
-
External APIs → retry, fallback, graceful degradation
-
Resource cleanup → RAII, finally blocks
-
User-facing operations → graceful error messages
Not Flagged (appropriate handling):
-
Error handling in /adapters/ , /api/ , /cli/ paths
-
Explicit logging before swallowing
-
Re-raise after logging
Flags
Flag Type Default Description
--kiss-only
bool false Run only KISS validation
--purity-only
bool false Run only purity validation
--solid-only
bool false Run only SOLID validation
--crash-only
bool false Run only Let It Crash validation
--no-kiss
bool false Skip KISS validation
--no-purity
bool false Skip purity validation
--no-solid
bool false Skip SOLID validation
--no-crash
bool false Skip Let It Crash validation
--threshold
int 10 Max cyclomatic complexity
--max-cognitive
int 15 Max cognitive complexity
--max-lines
int 50 Max function line count
--max-depth
int 4 Max nesting depth
--max-params
int 5 Max parameter count
--max-file-lines
int 300 Max file line count (SRP)
--max-interface-methods
int 7 Max interface methods (ISP)
--strict
bool false Treat all warnings as errors
--core-only
bool false Only validate core layer files
--all
bool false Analyze all files, not just changed
--report
bool false Generate detailed JSON report
Integration Flags
For use by other skills and pre-commit hooks:
Flag Description
--pre-commit
Run as pre-commit hook (blocks commit on failure)
--inline
Real-time validation during implementation
--json
Output JSON format for programmatic use
Exit Codes
Code Meaning Action
0 Validation passed Proceed
2 Violations detected Blocked - refactor required
3 Validation error Manual intervention needed
Personas Activated
-
code-warden - Primary reviewer for principles enforcement
-
optimizer - For complex refactoring guidance
-
guardian - For metrics integration
Validator Scripts
Located in scripts/ directory:
validate_kiss.py
python .claude/skills/sc-principles/scripts/validate_kiss.py
--scope-root .
--threshold 10
--max-lines 50
--json
validate_purity.py
python .claude/skills/sc-principles/scripts/validate_purity.py
--scope-root .
--core-only
--json
Pre-commit Integration
Add to .pre-commit-config.yaml :
repos:
- repo: local
hooks:
-
id: kiss-check name: KISS Validation entry: python .claude/skills/sc-principles/scripts/validate_kiss.py --scope-root . --json language: python types: [python] pass_filenames: false
-
id: purity-check name: Purity Validation entry: python .claude/skills/sc-principles/scripts/validate_purity.py --scope-root . --json language: python types: [python] pass_filenames: false
-
Refactoring Guidance
When violations are detected, apply these patterns:
For Complexity Violations
-
Extract Method - Split large functions into smaller, named pieces
-
Guard Clauses - Replace nested if/else with early returns
-
Strategy Pattern - Replace complex switch/if-else with polymorphism
-
Decompose Conditional - Name complex conditions as explaining variables
Before:
def process(data, config, user): if data: if config.enabled: if user.has_permission: for item in data: if item.valid: # deep logic here
After:
def process(data, config, user): if not can_process(data, config, user): return None return process_items(data)
def can_process(data, config, user): return data and config.enabled and user.has_permission
def process_items(data): return [process_item(item) for item in data if item.valid]
For Purity Violations
-
Dependency Injection - Pass dependencies as arguments
-
Repository Pattern - Isolate database operations
-
Adapter Pattern - Wrap external APIs
-
Return Don't Print - Return values, let callers handle output
Before:
In domain/calculator.py (CORE - should be pure)
def calculate_discount(user_id): user = db.query(User).get(user_id) # I/O in core! print(f"Calculating for {user.name}") # Side effect! return 0.2 if user.is_premium else 0.1
After:
In domain/calculator.py (CORE - now pure)
def calculate_discount(user: User) -> float: """Pure function - no I/O, just logic.""" return 0.2 if user.is_premium else 0.1
In adapters/discount_service.py (SHELL - I/O allowed)
def get_user_discount(user_id: int) -> float: user = user_repository.get(user_id) discount = calculate_discount(user) logger.info(f"Discount for {user.name}: {discount}") return discount
For SOLID Violations
-
SRP (Single Responsibility) - Extract into modules
-
OCP (Open-Closed) - Use strategy pattern or registry
-
LSP (Liskov Substitution) - Honor base contracts
-
ISP (Interface Segregation) - Split fat interfaces
-
DIP (Dependency Inversion) - Inject dependencies
Before (OCP violation):
def process_payment(payment_type, amount): if payment_type == "credit": return process_credit(amount) elif payment_type == "debit": return process_debit(amount) elif payment_type == "crypto": # New type = code change! return process_crypto(amount)
After (OCP compliant):
PROCESSORS = { "credit": process_credit, "debit": process_debit, "crypto": process_crypto, # Add here, no function change }
def process_payment(payment_type, amount): processor = PROCESSORS.get(payment_type) if not processor: raise ValueError(f"Unknown payment type: {payment_type}") return processor(amount)
For Let It Crash Violations
-
Remove catch-all blocks - Let bugs surface
-
Remove defensive guards - Validate at boundaries instead
-
Flatten try/except - Handle at edges, not everywhere
Before (catch-all anti-pattern):
def get_user(user_id): try: user = db.query(User).get(user_id) return user except: # BAD: catches everything, hides bugs return None
After (let it crash):
def get_user(user_id): # Let database errors surface - they indicate real problems return db.query(User).get(user_id)
Handle at the boundary (API layer)
@app.get("/users/{user_id}") def api_get_user(user_id: int): try: return get_user(user_id) except DBError as e: logger.error("Database error", error=e, user_id=user_id) raise HTTPException(500, "Database unavailable")
Examples
Example 1: Full Validation
$ /sc:principles src/ --report
KISS Validation: BLOCKED Files analyzed: 12 Errors: 3, Warnings: 5
Violations: [ERROR] src/services/order.py:45 process_order: complexity = 15 (max: 10) [ERROR] src/utils/parser.py:12 parse_data: length = 78 (max: 50) [WARNING] src/domain/calc.py:8 calculate: parameters = 7 (max: 5)
Purity Validation: BLOCKED Core violations: 2, Shell warnings: 1
Violations: [ERROR] src/domain/user.py:23 get_status: database - db.query (context: core) [ERROR] src/services/report.py:56 generate: file_io - open (context: core)
Recommendations:
- COMPLEXITY: Extract helper functions, use early returns
- DATABASE: Use repository pattern. Business logic receives data, not queries
- FILE I/O: Move file operations to adapter layer
Example 2: KISS Only
$ /sc:principles src/ --kiss-only --threshold 8
KISS Validation: PASSED Files analyzed: 12 Errors: 0, Warnings: 2
Example 3: Strict Mode
$ /sc:principles src/ --strict
All warnings become errors
Any warning will block
Quality Integration
This skill integrates with SuperClaude quality gates:
-
Contributes to simplicity dimension (weight: 0.08)
-
Contributes to purity dimension (weight: 0.02)
-
Triggers simplification strategy when simplicity < 70
-
Triggers purification strategy when purity < 70
Related Skills
-
/sc:improve --type principles
-
Auto-refactor for principles compliance
-
/sc:implement
-
Includes principles validation by default
-
/sc:analyze
-
Code analysis includes principles metrics
Test Coverage
The validators have comprehensive test coverage (59 tests across 4 suites):
Purity Validator Tests (16):
-
Async function detection (async def )
-
Await expression detection
-
Async for loop detection
-
Async with context manager detection
-
Shell vs core severity differentiation
-
False positive regression (set.add, parser.add_argument, asyncio.run, callback.call)
-
Edge cases (syntax errors, unicode, empty files)
KISS Validator Tests (15):
-
Function length (inclusive count)
-
Cyclomatic complexity
-
Cognitive complexity
-
Nesting depth
-
Parameter count
-
Edge cases (syntax errors, unicode, empty files)
SOLID Validator Tests (15):
-
SRP: File length, class method count, private methods excluded
-
OCP: isinstance cascades
-
LSP: NotImplementedError in concrete overrides, ABC/Protocol excluded
-
ISP: Fat Protocol and ABC interfaces
-
DIP: Direct service instantiation in core paths
-
Edge cases (syntax errors, empty files)
Crash Validator Tests (13):
-
Bare except detection
-
except: pass detection
-
Exception swallowed in core paths
-
Exception with re-raise (OK)
-
Specific exception handling (OK)
-
Nested try/except depth
-
Shell path relaxation (adapters, api)
-
Edge cases (syntax errors, empty files, try/finally)
Run tests:
python -m pytest .claude/skills/sc-principles/tests/ -v
Version: 2.1.0 Validators: validate_kiss.py , validate_purity.py , validate_solid.py , validate_crash.py
Shared: shared.py (common find_python_files utility) Agent: code-warden
Traits: principles-enforced
Tests: 59 passing (all 4 validators)