sc-principles
/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
1. INITIALIZE
├── Detect scope root
├── Find changed Python files (git diff)
└── Determine file contexts (core vs shell)
2. 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)
3. VALIDATE
├── Compare against thresholds
├── Classify violations (error vs warning)
└── Apply context rules (core stricter than shell)
4. 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_depthper 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
simplicitydimension (weight: 0.08) - Contributes to
puritydimension (weight: 0.02) - Triggers
simplificationstrategy when simplicity < 70 - Triggers
purificationstrategy 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)