code-smell-detector
Code Smell Detector
A smell is not a bug. It's a structural property that makes bugs more likely later. The code works today; the smell is about tomorrow.
Smell catalog — ranked by how often they actually matter
| Smell | Detection heuristic | Why it matters | Fix |
|---|---|---|---|
| Long method | > 40 lines, > 3 levels of nesting | Can't hold it in your head; every change risks the whole thing | Extract method |
| God class | > 15 methods, > 10 fields, or depends on > 8 other classes | Every change touches this file; merge conflicts; nobody understands it all | Split by responsibility |
| Feature envy | Method reads 3+ fields of another object, 0 of its own | The method is in the wrong class | Move method |
| Shotgun surgery | One conceptual change → edits in 5+ files | High coupling; change is expensive and error-prone | Consolidate the scattered concern |
| Primitive obsession | Three strings passed together (street, city, zip) everywhere |
The missing type is the abstraction you need | Introduce parameter object / value type |
| Boolean blindness | Method takes 3+ booleans; call sites look like f(true, false, true) |
Nobody knows what true means at the call site |
Named parameters, enum, or config object |
| Speculative generality | Abstract class with one concrete subclass; interface with one impl; hook method nobody overrides | Complexity paid for, never used | Inline it → dead-code-eliminator |
| Data clump | Same 3+ variables always appear together in signatures | Missing type (same as primitive obsession, across methods) | Introduce a class |
| Message chain | a.getB().getC().getD().doThing() |
Caller knows the entire object graph; any link change breaks it | Tell-don't-ask; hide the chain |
| Duplicated code | Two regions > 6 lines, > 80% similar | Bug fixed in one, not the other | Extract; but see FP note below |
Ranking — which smells to surface first
Don't dump all findings. Rank by change frequency × smell severity:
- God class that's edited weekly — urgent. Every week of delay costs more conflicts.
- Long method in a file untouched for 2 years — ignore. It's ugly but stable.
- Duplicated code where both copies are in hot files — high. The divergence-bug will happen.
Use git log --format= --name-only | sort | uniq -c | sort -rn to get change frequency. Smells in cold code are academic.
False-positive suppression
| Smell flagged | But actually fine when… |
|---|---|
| Long method | It's a flat sequence of independent steps (setup, setup, setup — a script) |
| God class | It's the app's composition root / DI container — by design, it knows everything |
| Duplicated code | The two copies have different reasons to change. "Coincidental duplication" should NOT be extracted — you'll couple two unrelated concerns |
| Speculative generality | It's a plugin interface and the second impl is on the roadmap this quarter |
| Message chain | The chain is navigating a well-known, stable schema (config.database.pool.size) |
Worked example
Code:
class OrderProcessor:
def process(self, order, user, warehouse, shipper, payment, notifier,
audit, retry, dry_run): # 9 params
if not dry_run:
if payment.charge(user.card, order.total):
if warehouse.has_stock(order.items):
warehouse.reserve(order.items)
if shipper.can_deliver(user.address.zip): # message chain
label = shipper.create_label(user.address.street,
user.address.city,
user.address.zip) # data clump
# ... 40 more lines ...
Findings:
- Long method (60 lines, 4 nesting levels) —
process()does validation, charging, reservation, shipping, notification, auditing. Six responsibilities. - Boolean blindness —
retry, dry_runas positional booleans. Call sites:processor.process(o, u, w, s, p, n, a, True, False). Which one isdry_run? - Data clump —
street, city, zipalways travel together. There's anAddressobject right there (user.address) — pass it whole. - Message chain —
user.address.zip. Minor here (Address is stable), but ifshippertook anAddress, this goes away with fix #3.
Prioritization: git log shows OrderProcessor edited 14 times last quarter. Long method + high churn → extract first.
Do not
- Do not report a smell without saying why it matters here. "This is a long method" is lint output. "This is a long method that 4 people edited last month and each edit touched a different 10-line region" is a finding.
- Do not extract coincidental duplication. If two copies look similar but serve different domains, they'll diverge — and then your shared helper has a boolean parameter and you're worse off.
- Do not flag smells in generated code, test fixtures, or migration scripts. Different rules.
- Do not recommend the fix without →
code-refactoring-assistantfor the mechanics. This skill finds; that one fixes.
Output format
<file>:<line> <SMELL> churn=<commits-last-90d>
<what the structure is>
<why it will cost you — specific to this instance>
<suggested refactoring — one sentence>
<false-positive check: does the suppression rule apply?>
---
Ranked by (churn × severity). Fix the top 3; ignore the rest until they climb.
More from santosomar/general-secure-coding-agent-skills
code-review-assistant
Performs structured code review on a diff or file set, producing inline comments with severity levels and a summary. Checks correctness, error handling, security, and maintainability — in that priority order. Use when reviewing a pull request, when the user asks for a code review, when preparing code for merge, or when a second opinion is needed on a change.
15dependency-resolver
Diagnoses and resolves package dependency conflicts — version mismatches, diamond dependencies, cycles — across npm, pip, Maven, Cargo, and similar ecosystems. Use when install fails with a resolution error, when two packages require incompatible versions of a third, or when upgrading one dependency breaks another.
4configuration-generator
Generates configuration files for services and tools (app config, logging config, linter config, database config) from a brief description of desired behavior, matching the target format's idioms. Use when bootstrapping a new service, when the user asks for a config file for a specific tool, or when translating config intent between formats.
3ci-pipeline-synthesizer
Generates CI pipeline configs by analyzing a repo's structure, language, and build needs — GitHub Actions, GitLab CI, or other platforms. Use when bootstrapping CI for a new repo, when porting from one CI to another, when the user asks for a pipeline that builds and tests their project, or when wiring in security gates.
3api-design-assistant
Reviews and designs API contracts — function signatures, REST endpoints, library interfaces — for usability, evolvability, and the principle of least surprise. Use when designing a new public interface, when reviewing an API PR, when the user asks whether a signature is well-designed, or when planning a breaking change.
2code-refactoring-assistant
Executes refactorings — extract method, inline, rename, move — in small, behavior-preserving steps with a test between each. Use when the user wants to restructure working code, when cleaning up after a feature lands, or when a smell has been identified and needs fixing.
2