CtrlK
BlogDocsLog inGet started
Tessl Logo

code-smell-detector

Identifies code smells — structural patterns that correlate with maintainability problems — and explains why each matters in context. Use when reviewing a PR for structural quality, when the user asks what's wrong with a piece of code that isn't buggy, or when prioritizing refactoring targets.

Install with Tessl CLI

npx tessl i github:santosomar/general-secure-coding-agent-skills --skill code-smell-detector
What are skills?

100

Quality

100%

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SKILL.md
Review
Evals

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

SmellDetection heuristicWhy it mattersFix
Long method> 40 lines, > 3 levels of nestingCan't hold it in your head; every change risks the whole thingExtract method
God class> 15 methods, > 10 fields, or depends on > 8 other classesEvery change touches this file; merge conflicts; nobody understands it allSplit by responsibility
Feature envyMethod reads 3+ fields of another object, 0 of its ownThe method is in the wrong classMove method
Shotgun surgeryOne conceptual change → edits in 5+ filesHigh coupling; change is expensive and error-proneConsolidate the scattered concern
Primitive obsessionThree strings passed together (street, city, zip) everywhereThe missing type is the abstraction you needIntroduce parameter object / value type
Boolean blindnessMethod takes 3+ booleans; call sites look like f(true, false, true)Nobody knows what true means at the call siteNamed parameters, enum, or config object
Speculative generalityAbstract class with one concrete subclass; interface with one impl; hook method nobody overridesComplexity paid for, never usedInline it → dead-code-eliminator
Data clumpSame 3+ variables always appear together in signaturesMissing type (same as primitive obsession, across methods)Introduce a class
Message chaina.getB().getC().getD().doThing()Caller knows the entire object graph; any link change breaks itTell-don't-ask; hide the chain
Duplicated codeTwo regions > 6 lines, > 80% similarBug fixed in one, not the otherExtract; but see FP note below

Ranking — which smells to surface first

Don't dump all findings. Rank by change frequency × smell severity:

  1. God class that's edited weekly — urgent. Every week of delay costs more conflicts.
  2. Long method in a file untouched for 2 years — ignore. It's ugly but stable.
  3. 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 flaggedBut actually fine when…
Long methodIt's a flat sequence of independent steps (setup, setup, setup — a script)
God classIt's the app's composition root / DI container — by design, it knows everything
Duplicated codeThe two copies have different reasons to change. "Coincidental duplication" should NOT be extracted — you'll couple two unrelated concerns
Speculative generalityIt's a plugin interface and the second impl is on the roadmap this quarter
Message chainThe 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:

  1. Long method (60 lines, 4 nesting levels) — process() does validation, charging, reservation, shipping, notification, auditing. Six responsibilities.
  2. Boolean blindnessretry, dry_run as positional booleans. Call sites: processor.process(o, u, w, s, p, n, a, True, False). Which one is dry_run?
  3. Data clumpstreet, city, zip always travel together. There's an Address object right there (user.address) — pass it whole.
  4. Message chainuser.address.zip. Minor here (Address is stable), but if shipper took an Address, 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-assistant for 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.
Repository
santosomar/general-secure-coding-agent-skills
Last updated
Created

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.