Skip to main content
Glama
adrianov

abcop_inspection

by adrianov

abcop

A must-have gate for AI-written code. Agents ship faster than humans can re-read; abcop keeps that code understandable by gating function and module ABC complexity so every unit fits a human head and an LLM context window. Diagnostics are also a hook for automated refactoring — extract a method, split a module — so CI rejects bad growth and points agents at concrete maintainability fixes.

One self-contained binary across Ruby, Rust, Python, Go, PHP, Java, C#, Dart, JavaScript, TypeScript, C, C++, Objective-C, Swift, Solidity, Zig and Haskell — no runtimes, no plugins, no per-language installs. Written in Rust for speed: one parse per file, one walk per metric, grammars compiled in; whole trees in milliseconds.

Install

brew install adrianov/abcop/abcop
cargo install abcop

macOS (.tar.gz from GitHub Releases; binary + man):

# Apple Silicon — use the *-x86_64-apple-darwin.tar.gz asset on Intel Macs
curl -LO https://github.com/adrianov/abcop/releases/download/v0.19.1/abcop-0.19.1-aarch64-apple-darwin.tar.gz
tar -xzf abcop-0.19.1-aarch64-apple-darwin.tar.gz
sudo cp abcop-0.19.1-aarch64-apple-darwin/abcop /usr/local/bin/
sudo mkdir -p /usr/local/share/man/man1
sudo cp abcop-0.19.1-aarch64-apple-darwin/abcop.1 /usr/local/share/man/man1/

Ubuntu / Debian (.deb from GitHub Releases; amd64, Ubuntu 22.04+ / Debian bookworm+):

# example for v0.19.1 — use the asset name from the release page
curl -LO https://github.com/adrianov/abcop/releases/download/v0.19.1/abcop_0.19.1-1_amd64.deb
sudo dpkg -i abcop_0.19.1-1_amd64.deb
man abcop

Related MCP server: readyorai

Example run

lib/sinatra/base.rb:1254:0: C: Metrics/AbcSize: Assignment Branch Condition size for `error_block!` is too high. [<7, 14, 9> 18.06/17]
src/main.rs: W: Metrics/ModuleAbcSize: Assignment Branch Condition size for module is too high. [<80, 200, 60> 228.04/120] -- extract a coherent subunit
132 files analysed in 0.09s, 7 abc offenses, 0 used-once offenses, 0 never-used warnings, 14 module-abc warnings

Why ABC, not line counts

Line counts lie. The ABC metric (Jerry Fitzpatrick, C++ Report, June 1997) counts what does work — assignments (A), branches (B), conditions (C) — as sqrt(A² + B² + C²). Fitzpatrick defined both method and module scope; abcop gates both:

Rule

Severity

Meaning

Metrics/AbcSize

C

function ABC above --max-abc (default 17)

Metrics/ModuleAbcSize

W

module ABC above --max-module-abc (default 120)

UsedOnce

W

local written once, read once — consider inlining

NeverUsed

W

local written, never read

A sparse wrapper and a dense god-object can share a line count; ABC separates them. Complexity is the gate — never a line budget. UsedOnce / NeverUsed are secondary.

Built for AI workflows

  • Understandable by construction. AbcSize 17 and ModuleAbcSize 120 keep every unit inside a human head and an LLM context window. Models edit small units reliably; when something breaks, the blast radius is tiny.

  • Refactoring hook. Exit code 1 plus stable JSON/JSONL diagnostics (rule, score, vector, message) feed agents and scripts: split oversized modules, extract hot methods. Each finding is an actionable maintainability step, not a style nit.

  • Fast enough for every push. Sub-second whole-tree scans — including code an agent will never re-read tomorrow.

  • Signal only. No formatting or style cops. Vendored, generated and test trees are skipped by default. Two knobs: --max-abc and --max-module-abc.

  • Prefer MCP with LLMs. abcop --mcp is the best way to use abcop from an agent: the model gets offenses as soon as it writes, so it can fix complexity and dead locals in the same turn and ship effective code right away — not after a later CLI/CI pass. Official Rust rmcp SDK; tool abcop_inspection. Listed on the MCP Registry as io.github.adrianov/abcop (crates.io package + each GitHub v* tag).

Inspired by RuboCop (Ruby AbcSize parity and # rubocop:disable directives) and lizard (one tool, many languages). Ruby's counting matches RuboCop 1.89 byte-for-byte (scripts/compare_parity.py).

Advantages

  • ~850k–940k LOC/s per core (Apple M1 Pro). RuboCop's lib tree (943 files, 110k LOC): 0.13 s — ~39× faster than rubocop --only Metrics/AbcSize (cache off), ~140× a full rubocop run.

  • One binary, zero deps — grammars and an embedded result cache (cache.redb) compiled in; works on clean CI images with no language toolchains.

  • Deterministic — same findings every run; text / JSON / JSONL stream as files finish; --sort-by-score buffers for worst-first emit.

Usage

abcop [OPTIONS] PATH...

Option

Default

Meaning

[PATH]...

auto

targets; omitted → scope selection

--max-abc N

17

function ABC ceiling

--max-module-abc N

120

module ABC ceiling

--only abc|used-once|never-used

all

single check

--full

off

whole production tree (default skips stay on)

--everything

off

no gitignore / hidden / vendored pruning

--format text|json|jsonl

text

CI-friendly output

--sort-by-score

off

highest ABC first

--mr

off

MR scope (uncommitted + branch vs base)

--uncommitted

off

working-tree + index + untracked vs HEAD only

--no-cache

off

skip on-disk cache

--mcp

off

MCP server on stdio (for AI clients)

--dump-tree FILE

—

debug syntax tree

Exit codes: 0 clean, 1 findings, 2 usage error.

abcop app lib                          # two trees
abcop --format jsonl lib > abcop.jsonl # streaming CI / agent input
abcop --only used-once src             # inline candidates
abcop --max-abc 12 --only abc lib      # stricter function budget
abcop --max-module-abc 80 lib           # stricter module budget
abcop --sort-by-score --only abc lib   # worst first
abcop --uncommitted                    # pre-commit / agent loop
abcop --mr --only abc                  # this branch's touched units
abcop --mcp                            # MCP server on stdio (for AI clients)

JSON diagnostics include file, line, column, severity, rule, message, plus score / vector for ABC rules:

{"rule":"Metrics/AbcSize","score":10.0,"vector":"<6, 8, 0>"}
{"rule":"Metrics/ModuleAbcSize","score":120.5,"vector":"<40, 100, 40>"}

Scope selection

Named paths — those targets only.

Omitted — narrowest useful scope, announced on stderr:

  1. uncommitted work vs HEAD if the tree is dirty

  2. else current MR (--mr forces this)

  3. else full tree (outside a repo)

Default walks prune test/fixture trees, vendored/build output (vendor/, node_modules/, target/, …), db/migrate/, route tables (config/routes.rb, config/routes/*.rb), and generated names (*.min.js, *_pb.go, …). Name a path explicitly to scan it anyway. Third-party, route-table, and fixture paths are never scoped review surface — a diff through vendor/ or tests/fixtures/ does not make that material owned code.

Scoped ModuleAbcSize re-sums only methods that intersect the diff and compares that total to --max-module-abc (default 120; untracked = every method). A small patch into an oversized legacy file stays quiet unless the touched methods themselves exceed the ceiling; AbcSize still reports any changed method over --max-abc. Full scans (--full, --everything) report every production module over the ceiling; ModuleAbcSize still exempts test trees on full scans (scoped runs can flag them when changed methods sum over the limit). UsedOnce / NeverUsed always follow the changed lines.

On a dirty tree the bare default is uncommitted-only; --mr takes the full branch union. Commits straight to main use a 36-hour window when no branch base applies. --uncommitted fails outside a repository instead of silently widening.

Directives

Everywhere except Rust, RuboCop-style # / // suppressions work (trailing and block; bare Metrics allowed). rubocop:disable-next is ignored, matching rubocop.

def legacy_path # rubocop:disable Metrics/AbcSize
  ...
end

MCP (Model Context Protocol)

Preferred for LLM / agent workflows. Wire abcop as an MCP server so the model can call abcop_inspection while it edits: feedback arrives in the same turn, the agent corrects soon, and it writes maintainable code on the first pass instead of discovering ABC / UsedOnce / NeverUsed only in CI.

abcop --mcp runs a long-lived MCP server on stdio — same idea as RuboCop’s MCP and rrubocop, with no Ruby mcp gem. Tool:

Tool

Purpose

abcop_inspection

Analyse via path / paths (string or array) and/or inline source_code; returns compact offense JSON (code, line, column, message; score / vector for ABC rules). Always pass an explicit project path — omitting targets errors out (avoids scanning $HOME when MCP cwd is mis-set).

  • MCP Registry name: mcp-name: io.github.adrianov/abcop

Metadata lives in server.json, which is part of the Cargo package on crates.io. Each GitHub v* release tag publishes that listing to the MCP Registry (after the crates.io upload).

Example client config (Cursor / VS Code / Windsurf):

{
  "mcpServers": {
    "abcop": {
      "type": "stdio",
      "command": "abcop",
      "args": ["--mcp"],
      "cwd": "/path/to/your/project"
    }
  }
}

Claude Code:

claude mcp add abcop -- abcop --mcp

Intended for MCP clients, not interactive use.

Caching

Content-addressed cache under $XDG_CACHE_HOME/abcop (or ~/.cache/abcop; override with ABCOP_CACHE_DIR). Warm reruns ~5× faster. Keys cover contents, version, rule revision, threshold, checks and path — no cross-project collisions. Auto-pruned to 20 000 entries; --no-cache disables. Nothing is written inside the project.

Supported languages

Seventeen languages plus embedded JS in HTML/templates, four rules each, one CI gate.

Language

Files

Notes

Ruby

.rb .rake .ru .gemspec, Gemfile, Rakefile, …

RuboCop-parity AbcSize

Rust

.rs

Python

.py .pyi .pyw

Go

.go

PHP

.php

Java

.java

C#

.cs

Solidity

.sol

Dart

.dart

Zig

.zig

Haskell

.hs .lhs

JavaScript

.js .mjs .cjs .jsx

also <script> in HTML/templates

TypeScript

.ts .tsx .mts .cts

lang="ts" / typescript script types

Embedded JS

.html .htm .xhtml .erb .slim .haml .pug .vue .svelte .ejs .njk .jinja .j2 .twig .liquid .hbs …, *.js.erb

extract scripts; blank <% %>, {{ }}, {% %} holes

C / C++

.c .h .cc .cpp .cxx .hpp .hxx .hh

.h via C++ grammar

Objective-C

.m .mm

Swift

.swift

Named declarations are measured units; anonymous function-likes roll into the enclosing unit; nested units never double-count. C-family: file-scope globals are out of single-file reach; loop-head locals are protocol; field writes also read the object; export-macro class forms (class UTIL_EXPORT Foo) are skipped for variable rules.

Benchmarks

Apple M1 Pro, warm cache:

Corpus

Size

abcop

rubocop --only AbcSize¹

Full rubocop

rubocop/lib

943 files, 110k LOC

0.13 s

5.1 s (~39×)

18.3 s

cargo registry sample

6,603 files, 2.6M LOC

~2.8 s

—

—

¹ --cache false — rubocop's own cache otherwise makes repeats look free.

Development

cargo test
cargo build --all-targets   # zero warnings
abcop src                   # dogfood

Shared engine: src/scope_model/ (backend, walk, eval); language collectors sit beside each backend (clike/, sollang/, …). Probe grammar nodes with abcop --dump-tree FILE. Parity: scripts/compare_parity.py.

License

GNU GPL v3 or later (SPDX: GPL-3.0-or-later). See LICENSE.

Copyright © 2026 Peter Adrianov. All rights reserved.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables local code inspection and analysis for clean code practices, best practices, and actionable recommendations without external API calls or modifying the code.
    23 npm
    1
    MIT
  • F
    license
    Not graded
    quality
    B
    maintenance
    Enables coding agents and developers to parse Python source into ASTs and compute complexity metrics that pinpoint refactoring hotspots, dead code, and breaking API diffs. It returns decomposition plans and dependency-graph telemetry over MCP, keeping token usage low with zero external dependencies.
    7
    -