skills/code-smell-detector/SKILL.md
Detects and fixes common code smells during review or refactoring. Invoke whenever reviewing code for quality issues, before merging a PR, when refactoring legacy code, or when the user asks about code quality, anti-patterns, or technical debt. Detects: over-abstraction, complex inheritance, large functions, tight coupling, hidden dependencies, magic numbers, boolean traps, swallowed exceptions, global state, and duplicate code. Provides specific fixes with before/after examples. Also invoke when someone says "review this code", "is this clean?", "can I improve this?", "this feels messy", or "find problems in my code".
npx skillsauth add rory-data/copilot code-smell-detectorInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This skill identifies common anti-patterns and provides constructive, specific fixes. It ensures code maintains simplicity, clear responsibility, and testability.
Relation to existing instructions: The instruction files in
instructions/core/define preventative rules (what to do when writing new code). This skill is detective — use it when reviewing or refactoring existing code. Where a smell maps to an instruction file rule, a cross-reference is noted so you understand the canonical guidance.
Why it matters: Unnecessary abstraction adds complexity without benefit. Every layer must justify its existence — if you can't name a concrete reason to have it, it shouldn't exist.
Red flags:
Example:
# SMELL: Abstraction for one implementation
class DataProcessor(ABC):
@abstractmethod
def process(self, data): pass
class SimpleDataProcessor(DataProcessor):
def process(self, data):
return data * 2
# FIX: Direct implementation
def process_data(data):
return data * 2
Detection rules:
Cross-reference:
instructions/core/engineering-principles.instructions.md(YAGNI & KISS)
Why it matters: Deep inheritance chains obscure what code does and who owns what. Composition makes dependencies explicit and code independently testable.
Red flags:
Example:
# SMELL: Inheritance chain
class Entity:
def save(self): pass
class TimestampedEntity(Entity):
def timestamp(self): pass
class AuditableEntity(TimestampedEntity):
def audit(self): pass
class User(AuditableEntity):
def authenticate(self): pass
# FIX: Composition
class User:
def __init__(self, storage, timestamps, audit):
self.storage = storage
self.timestamps = timestamps
self.audit = audit
def save(self):
self.storage.save(self)
self.timestamps.record()
self.audit.log("saved user")
Detection rules:
super() calls at multiple levels → flagCross-reference:
instructions/core/engineering-principles.instructions.md(Dependency Inversion). For GoF structural patterns (Decorator, Proxy, etc.) as alternatives, invoke thegang-of-fourskill.
Why it matters: Large functions do multiple things, making them hard to name, test, and modify. If a function is hard to summarise in one sentence, it should be split.
Red flags:
Example:
# SMELL: One function doing everything
def process_user_data(user_dict, validate=True, save=True, notify=True):
if validate:
if not user_dict.get('email'):
raise ValueError("Email required")
# ... more validation
user = User(...)
if save:
db.save(user)
if notify:
email_service.send(user.email, "Welcome!")
return user
# FIX: Separate concerns, compose at the top level
def validate_user_data(user_dict): ...
def create_user(user_dict): ...
def process_user_data(user_dict):
validate_user_data(user_dict)
user = create_user(user_dict)
db.save(user)
email_service.send(user.email, "Welcome!")
return user
Cross-reference:
instructions/core/engineering-principles.instructions.md(Function Size) andinstructions/core/coding-style.instructions.md(Code Quality Checklist).
Why it matters: When a class instantiates its own dependencies, you cannot test it in isolation and cannot swap implementations. Coupling two things together means changing one risks breaking the other.
Red flags:
ServiceName() instantiated inside a methodobj.service.repo.data (3+ dots)Example:
# SMELL: Hidden dependency
class UserService:
def create_user(self, name, email):
db = Database() # Impossible to swap for tests
email_svc = EmailService()
user = db.save(name, email)
email_svc.send(email, "Welcome!")
return user
# FIX: Inject dependencies
class UserService:
def __init__(self, db, email_svc):
self.db = db
self.email_svc = email_svc
def create_user(self, name, email):
user = self.db.save(name, email)
self.email_svc.send(email, "Welcome!")
return user
Cross-reference:
instructions/core/engineering-principles.instructions.md(Dependency Inversion).
__all__ Exports (Python-specific)Why it matters: Without __all__, a module's public interface is ambiguous. Consumers
may accidentally import internal helpers, making refactoring harder.
Red flags:
__all__ in __init__.py_) re-exported accidentallyExample:
# SMELL: No public interface defined
from .core import process_data, _internal_helper
from .utils import validate_input, LOG_LEVEL
# FIX: Explicit public API
from .core import process_data
from .utils import validate_input
__all__ = ['process_data', 'validate_input']
Why it matters: Bare literals reveal nothing about intent. When the same literal appears in multiple places, a future change silently misses one.
Red flags:
Example:
# SMELL
if retries > 3:
time.sleep(5)
# FIX
MAX_RETRIES = 3 # Based on P99 network reliability measurements
RETRY_DELAY_SEC = 5 # Lambda max 15s; leaves headroom for retries
if retries > MAX_RETRIES:
time.sleep(RETRY_DELAY_SEC)
Cross-reference:
instructions/core/coding-style.instructions.md(Code Quality Checklist: "No hardcoded values").
Why it matters: Boolean parameters that change a function's fundamental behaviour are a sign the function does two things. Call sites become unreadable.
Red flags:
process(data, True, False, True) — positional booleans are unreadableExample:
# SMELL: Flag changes fundamental behaviour
def render(data, as_html=False):
if as_html:
return f"<div>{data}</div>"
return str(data)
# FIX: Separate named functions
def render_text(data): return str(data)
def render_html(data): return f"<div>{data}</div>"
Why it matters: Catching and ignoring exceptions hides real failures. Code that silently returns a default on error is lying to its callers.
Red flags:
except Exception: passexcept Exception: return {}except: pass (bare except)Example:
# SMELL: Silent failure
def load_config(path):
try:
return json.load(open(path))
except Exception:
return {} # Hides missing file, corrupt JSON, permissions errors
# FIX: Explicit handling with context
def load_config(path: str) -> dict:
try:
with open(path) as f:
return json.load(f)
except FileNotFoundError:
raise ConfigError(f"Config file not found: {path}")
except json.JSONDecodeError as exc:
raise ConfigError(f"Invalid JSON in {path}: {exc}") from exc
Cross-reference:
instructions/core/engineering-principles.instructions.md(Error Handling).
Why it matters: Global variables make execution order unpredictable and tests order-dependent. Any function that reads or writes global state becomes an implicit dependency of every caller.
Red flags:
global x inside their bodyExample:
# SMELL: Global mutable cache
_cache = {}
def get_user(user_id):
if user_id not in _cache:
_cache[user_id] = db.fetch(user_id)
return _cache[user_id]
# FIX: Pass state explicitly
class UserRepository:
def __init__(self, db, cache: dict | None = None):
self.db = db
self._cache = cache if cache is not None else {}
def get(self, user_id):
if user_id not in self._cache:
self._cache[user_id] = self.db.fetch(user_id)
return self._cache[user_id]
Why it matters: Every duplicated block is a future maintenance hazard. When the logic needs to change, you must find and update every copy.
Red flags:
Example:
# SMELL: Same validation in two places
def process_payment(amount):
if amount <= 0:
raise ValueError("Amount must be positive")
...
def process_refund(amount):
if amount <= 0:
raise ValueError("Amount must be positive")
...
# FIX: Extract once
def _require_positive_amount(amount: float) -> None:
if amount <= 0:
raise ValueError("Amount must be positive")
def process_payment(amount): _require_positive_amount(amount); ...
def process_refund(amount): _require_positive_amount(amount); ...
Cross-reference:
instructions/core/engineering-principles.instructions.md(DRY).
__all__ in Python packages| Severity | Criteria | |----------|----------| | Critical | Breaks testability, causes data loss, or masks errors | | Major | Significantly hampers readability or makes change risky | | Minor | Style issue; improvement but not urgent |
Swallowed exceptions and global state → Critical
Tight coupling, large functions → Major
Over-abstraction, missing __all__ → Major or Minor depending on scope
For each smell found:
| Symptom | Smell | Primary Fix |
|---------|-------|-------------|
| ABC with one impl | Over-abstraction | Delete the abstract class |
| Class hierarchy > 2 deep | Complex inheritance | Composition + injection |
| Function > 50 lines | Large function | Extract helpers |
| Foo() inside a method | Tight coupling | Constructor injection |
| No __all__ in __init__.py | Missing exports | Add __all__ |
| Bare numeric/string literals | Magic values | Named constants |
| func(data, True, False) | Boolean trap | Split into two functions |
| except Exception: pass | Swallowed exception | Raise with context |
| Module-level mutable variable | Global state | Pass as parameter or inject |
| Same block in 2+ places | Duplicate code | Extract shared function |
# SMELL
class StringUtils:
@staticmethod
def clean(s): return s.strip().lower()
# FIX: Module-level function
def clean_string(s): return s.strip().lower()
# SMELL
class UserManager:
def create(self): ...
def update(self): ...
def validate(self): ...
def send_email(self): ...
# FIX: Split by responsibility
class UserService: # lifecycle operations
class UserValidator: # validation only
class UserNotifier: # notifications only
# SMELL: 200-line function with mixed concerns
def process_order(order_data, validate, save, notify, log): ...
# FIX: Composed workflow of focused functions
def process_order(order_data):
validate_order(order_data)
order = build_order(order_data)
save_order(order)
notify_customer(order)
tools
Queries, manages, and troubleshoots Apache Airflow using the af CLI. Covers listing DAGs, triggering runs, reading task logs, diagnosing failures, debugging DAG import errors, checking connections, variables, pools, and monitoring health. Also routes to sub-skills for writing DAGs, debugging, deploying, and migrating Airflow 2 to 3. Use when user mentions "Airflow", "DAG", "DAG run", "task log", "import error", "parse error", "broken DAG", or asks to "trigger a pipeline", "debug import errors", "check Airflow health", "list connections", "retry a run", or any Airflow operation. Do NOT use for warehouse/SQL analytics on Airflow metadata tables — use analyzing-data instead.
tools
Build Airflow 3.1+ plugins that embed FastAPI apps, custom UI pages, React components, middleware, macros, and operator links directly into the Airflow UI. Use this skill whenever the user wants to create an Airflow plugin, add a custom UI page or nav entry to Airflow, build FastAPI-backed endpoints inside Airflow, serve static assets from a plugin, embed a React app in the Airflow UI, add middleware to the Airflow API server, create custom operator extra links, or call the Airflow REST API from inside a plugin. Also trigger when the user mentions AirflowPlugin, fastapi_apps, external_views, react_apps, plugin registration, or embedding a web app in Airflow 3.1+. If someone is building anything custom inside Airflow 3.1+ that involves Python and a browser-facing interface, this skill almost certainly applies.
data-ai
Use when the user needs human-in-the-loop workflows in Airflow (approval/reject, form input, or human-driven branching). Covers ApprovalOperator, HITLOperator, HITLBranchOperator, HITLEntryOperator, HITLTrigger. Requires Airflow 3.1+. Does not cover AI/LLM calls (see airflow-ai).
testing
Create new skills, modify and improve existing skills, and measure skill performance. Use when users want to create a skill from scratch, edit, or optimize an existing skill, run evals to test a skill, benchmark skill performance with variance analysis, or optimize a skill's description for better triggering accuracy.