Skip to main content

spaghetti-detector

Spaghetti code and architectural-smell detector.

Scans workspace packages for anti-patterns, architectural violations, and structural code smells — from single-function issues (long functions, deep nesting, high cyclomatic complexity) up to whole-package issues that only show up once you can see across files: real circular imports (not just a parent/child heuristic), copy-pasted function bodies, and the sync/async "twin" duplication pattern (load/aload, foo/foo_async) where a fix applied to one twin silently never reaches the other.

Every requested package is reviewed concurrently — one agent per package and then folded into a single consolidated report.

The package registry is generic and configurable via a YAML file, ad-hoc CLI flags, or both — see Configuring Packages.

Why It Exists

AI-generated spaghetti code — often referred to as "slop code" — is extremely common because Large Language Models (LLMs) prioritise immediate functional completion (the "happy path") over long-term software architecture. Whilst it looks syntactically perfect and heavily commented, it often suffers from structural issues:

  • Monolithic structures: AI tends to dump vast amounts of logic into single, giant files rather than separating concerns.
  • Copy-paste duplication: Instead of refactoring code into reusable functions, LLMs often repeat the same block of code with minor variations.
  • Accidental complexity: Because AI lacks system-wide perspective, it wires features together in a highly coupled way.
  • Hallucinated dependencies: A significant risk where AI suggests the use of non-existent libraries or packages.

This prevalence stems from the fundamental nature of AI training: LLMs are optimised to predict the next logical token based on probability, not to design maintainable software. While an AI can produce a functioning script quickly, it lacks the intuitive foresight that experienced developers use to build modular and scalable applications.

Human-written spaghetti code is extremely common and has existed since the dawn of programming. Whilst AI creates messy code due to a lack of situational awareness, humans usually create it due to time pressure, changing requirements, or a lack of experience.

Why Humans Write Spaghetti Code

  • Tight deadlines: Developers rush to ship features, prioritising speed over clean architecture.
  • Scope creep: Constantly adding new features to an old system without rewriting the base structure.
  • Skill gaps: Junior developers may not yet understand design patterns or how to separate concerns.
  • The "copy-paste" habit: Reusing working blocks of code across a project instead of building reusable functions.
  • Lack of code reviews: Teams skipping peer reviews, allowing messy logic to slip into production.

AI vs. Human Spaghetti Code

  • Human style: Often features massive nested loops, confusing variable names (like x or data1), and forgotten TODO notes.
  • AI style: Usually looks highly professional, has perfect indentation, and includes beautiful comments, but the underlying logic is deeply tangled and redundant.

How spaghetti-detector Helps

Every problem above maps to one or more mechanically-enforced rules. The detector does not guess — it measures concrete thresholds and reports exact violations.

Problem → Rule Mapping

Problem Detector Rules What It Catches
Monolithic structures god-class, god-module, long-function, long-file, deep-nesting Classes with 25+ methods, files over 400 lines, functions exceeding 50 lines, nesting beyond 5 levels
Copy-paste duplication duplicate-function-body, sync-async-duplication Identical function bodies (5+ lines), sync/async twin pairs with ≥60% text similarity
Accidental complexity high-complexity, excessive-returns, message-chain, deep-inheritance, excessive-decorators Cyclomatic complexity above 10, functions with 4+ return paths, chained calls deeper than 3 levels, inheritance exceeding 4 levels
Layering violations layer-violation, transport-in-library, import-cycle, encapsulation-violation Library code importing transport frameworks, circular import chains, accessing private attributes across objects
Type safety gaps missing-return-type, missing-param-type, untyped-dict, bare-except Public functions missing annotations, bare dict in type hints, bare except: clauses
Dead code & clutter dead-code, unused-import, star-import, todo-marker, magic-number, magic-string Unreachable statements after return/raise/break, from x import *, unexplained numeric literals, repeated string comparisons standing in for a category code

From Detection to Remediation

The detector produces a consolidated report with a health score and grade per package:

  Package          Grade   Score   Files   KLOC   Issues
  ──────────────── ───── ───────  ────── ────── ───────
  boti-data           B    78.3       18   3.2       12
  etl-core            A    92.1       14   2.8        5
  OVERALL             B    82.5       32   6.0       17

Use --plan to get a prioritised remediation roadmap scored by severity_weight × fix_effort:

uv run spaghetti --plan --top 10
  #   Pri  Rule                           Sev  Effort     Issues  Score
  ─── ──── ────────────────────────────── ──── ───────── ──────  ─────
  1   P0   import-cycle                   ✖    major        3   30.0
  2   P0   god-class                      ✖    major        2   30.0
  3   P1   high-complexity                ⚠    moderate     5   15.0
  4   P1   long-function                  ⚠    moderate     4   12.0

This ensures you fix structural issues (circular imports, god-classes) before cosmetic ones (missing type hints, magic numbers) — maximising impact per unit of effort.

Usage

uv run spaghetti
uv run spaghetti --packages package1 package2
uv run spaghetti --severity error
uv run spaghetti --top 10 --exclude tests/ examples/
uv run spaghetti --json > report.json
uv run spaghetti --plan --top 10
uv run spaghetti --config spaghetti.yaml
uv run spaghetti --package my-lib=my-lib/src/my_lib
uv run spaghetti -q

Exit codes: 0 (clean), 1 (warnings present), 2 (errors present) — safe to wire into CI as a gate.

Options

Flag Default Description
--config none YAML file with a packages: {name: path} mapping (see below); replaces the built-in defaults
--package none Add or override one package as NAME=PATH (repeatable); applied on top of --config or the defaults
--packages all resolved packages Names to scan from the resolved registry
--severity info Minimum severity to display (info / warning / error)
--json off Output as JSON instead of the console report
--quiet / -q off Suppress the report banner (text-report mode only; --json is unaffected)
--top 5 Number of worst files to list per package
--exclude none Path substrings to exclude from scanning
--min-duplicate-lines 5 Minimum function length to consider for duplicate-body detection
--twin-similarity 0.6 Minimum text-similarity ratio (0–1) to flag a sync/async twin pair
--plan off Output a prioritised remediation plan instead of the standard report
--version Print the installed spaghetti-detector version and exit

Run uv run spaghetti --help for the full list.

Inline Suppression

Suppress specific findings on a line with # spaghetti-ignore[rule]:

# Suppress a specific rule
def f():  # spaghetti-ignore[long-function]: intentionally large
    ...

# Suppress all rules on a line
x: dict = {}  # spaghetti-ignore: reviewed, no issue

The marker applies to the line it appears on and the line directly above it (so a marker can sit above a def line too long for a trailing comment). Suppressed findings are counted in the report (suppressed: N in the header) rather than silently dropped — they remain visible.

JSON Output

With --json, the report is a single JSON object to stdout:

{
  "issues": [
    {
      "file": "src/my_module.py",
      "line": 42,
      "severity": "warning",
      "rule": "long-function",
      "message": "my_func() is 65 lines (max 50)",
      "package": "my-lib"
    }
  ],
  "suppressed": 3
}

Remediation Plan

With --plan, the detector outputs a prioritised fix order instead of the standard report. Each rule is scored by severity_weight × fix_effort and grouped into priority levels (P0–P3):

uv run spaghetti --plan --top 10

Priority levels:

  • P0 (score ≥ 12): CRITICAL — fix immediately (e.g., circular imports, god-classes)
  • P1 (score ≥ 7): HIGH — fix this sprint
  • P2 (score ≥ 3): MEDIUM — plan for next cycle
  • P3 (score < 3): LOW — track in backlog

The plan groups issues by rule, counts affected files, and lists a recommended fix order. This makes it easy to start a code-quality improvement cycle with the highest-impact fixes first.

Rules

The detector checks 39 rules across four tiers:

Per-file AST checks (31 rules): long-function, high-complexity, missing-return-type, missing-param-type, too-many-params, excessive-returns, boolean-flag-params, deep-nesting, untyped-dict, unused-import, swallowed-exception, duplicate-branch, encapsulation-violation, god-class (also factors in WMC — total cyclomatic complexity summed across a class's methods — alongside method/attribute counts), layer-violation, transport-in-library, potential-circular-import, god-module, mutable-default, bare-except, star-import, global-mutable, scope-mutation, dead-code, message-chain, excessive-decorators, magic-number, magic-string, missing-else, lazy-class, deep-inheritance, pass-through-method.

Per-file source-text checks (2 rules): long-file, todo-marker.

Infrastructure checks (1 rule): syntax-error (files that fail ast.parse()).

Per-package cross-file checks (5 rules): import-cycle, high-coupling (a module with both high fan-in and high fan-out), orphan-interface (an abstract class with exactly one implementation), duplicate-function-body, sync-async-duplication.

A low-cohesion (LCOM4) check also exists in the codebase but is intentionally not enabled by default — see the rule's own docstring for why.

See SDD.md for the full rule catalog, thresholds, and scoring formula.

Configuring Packages

With no flags, spaghetti auto-discovers packages from the current directory: every immediate subdirectory containing at least one .py file becomes its own named package (skipping .venv, .git, __pycache__, node_modules, and similar noise directories), and any loose .py files sitting directly in the current directory are grouped into one more package named after the directory itself. To point it at other packages — in this workspace, another workspace, or any directory on disk — use --config and/or --package.

Precedence:

  1. Neither flag given → cwd auto-discovery is used, as described above.
  2. --config given → its packages: mapping is used as the full set, explicitly rather than auto-discovered.
  3. --package NAME=PATH entries are then overlaid on top of whichever set (1) or (2) produced — adding new names or overriding ones already defined, so a config file plus a quick ad-hoc addition both work together.

--config: YAML File

# spaghetti.yaml
packages:
  my-lib: my-lib/src/my_lib
  my-service: services/my-service/src/my_service

Paths resolve relative to the config file's own directory, not the caller's working directory, so the same config works no matter where you invoke spaghetti from.

uv run spaghetti --config spaghetti.yaml

--package: Ad-hoc CLI Entries

uv run spaghetti --package my-lib=my-lib/src/my_lib --package other=../other/src/other

Repeatable; paths resolve relative to the current directory. Combine with --config to override or extend a config file for one run without editing it.

Development

uv run pytest spaghetti/tests/

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

spaghetti_detector-1.4.0.tar.gz (72.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

spaghetti_detector-1.4.0-py3-none-any.whl (53.7 kB view details)

Uploaded Python 3

File details

Details for the file spaghetti_detector-1.4.0.tar.gz.

File metadata

  • Download URL: spaghetti_detector-1.4.0.tar.gz
  • Upload date:
  • Size: 72.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for spaghetti_detector-1.4.0.tar.gz
Algorithm Hash digest
SHA256 58db784909579b79fa660222825563f22ef2a17e296918c8008ade0b498b2fe1
MD5 60dfd33815dffba47319b6d99f40f49e
BLAKE2b-256 1ae71dc670caac742ca6048aeaff73c79bb98664cf9abf37f9b27cca791b15aa

See more details on using hashes here.

File details

Details for the file spaghetti_detector-1.4.0-py3-none-any.whl.

File metadata

  • Download URL: spaghetti_detector-1.4.0-py3-none-any.whl
  • Upload date:
  • Size: 53.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for spaghetti_detector-1.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a4e48b06b774b426546adee9606077dd1c45b1496d5bf64b50caa1952ce24c04
MD5 01fa014e1b683db9d33c0b34bd3d04b8
BLAKE2b-256 195eb3dd5128bfc02dc3f0f8db6548124558decd924ddf9a1a247b51e2a835de

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

1.4.0 This release

2 files

1.3.2

2 files

1.3.1

2 files

1.3.0

2 files

1.2.2

2 files

1.2.1

2 files

1.2.0

2 files

1.1.2

2 files

1.1.1

2 files

1.0.7

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page