maintainability-agent docs
Reference

The analyzer pool: inventory, depth and license policy

The catalog of every code-analysis tool this agent knows about, how the pool is narrowed at run time, and what is fact versus what is still work.

The machine-readable form is src/maintainability_audit/_assets/analyzer-catalog.json, regenerated by tools/build_catalog.py. This page explains it; the JSON is the authority.

What is in the catalog

760 tools. 755 from the analysis-tools.dev database at commit 66668c6, captured 2026-06-10 (the database itself is CC0), plus 5 this project installed and ran that the database does not list.

Every count on this page is read from src/maintainability_audit/_assets/analyzer-catalog.json and held to it by test_the_pool_document_states_the_catalogs_own_counts — they drifted by a tool and a licence class before that test existed, which is exactly long enough for a reader to start trusting them.

Every entry carries the source's own facts — name, license, languages, homepage — and two fields that are this project's judgment, not the source's: tier and adapter.

By license class

License strings in the wild are messy: 89 distinct spellings, from MIT License to GPL with Classpath exception / MIT License to NOASSERTION. They are classified by explicit pattern, and anything unmatched stays unverified rather than being guessed.

Class Count Meaning
permissive 466 MIT, BSD, Apache, ISC, Zlib, Boost, Artistic, UPL and similar
unverified 72 License could not be confirmed from the source data. Not a claim that the tool is unfree — flake8 (MIT) lands here because GitHub could not map its text to an SPDX id
strong-copyleft 72 GPL, AGPL, CC-BY-SA, EUPL
proprietary 70 Paid, no free tier recorded
weak-copyleft 43 LGPL, MPL, EPL, CDDL
proprietary-free-tier 32 Commercial, with a free or open-source plan recorded in the source's plans block
source-available 5 Elastic License, Business Source License, Sustainable Use License. Not open source — visible source with restricted commercial use

A dual license takes the most permissive class on offer, because the licensee chooses: GPL v3 or Perl Artistic License 2.0 classifies as permissive.

PMD is an explicit catalog override in this tranche: its BSD-style license is verified against the upstream PMD LICENSE file, rather than inferred from the source catalog's Other value. Checkstyle is the matching weak-copyleft override: its LGPL-2.1-or-later license is verified against the upstream Checkstyle LICENSE file. SpotBugs is already classified weak-copyleft from the upstream catalog's GNU Lesser General Public License v2.1.

None of this is legal advice. Whether invoking a GPL analyzer as a separate process has any effect on the code it scans is a question for your own counsel. The classification exists so that whatever answer your organization reaches can be enforced, rather than left to whichever engineer installed what.

Eligible pool

448 tools are eligible: open-source license class, not deprecated, targets at least one language, and not security-only. Security scanning is secure-code-agent's job — those tools are cataloged and marked, never run from here.

Eligible by class: 368 permissive, 45 strong-copyleft, 35 weak-copyleft.

The three run-time selectors

Independent questions, and the pool is their intersection. Concerns asks what you want examined, depth how much work you want done, license policy what your organization is allowed to run. A user never names a tool.

Concerns

Concern Covers
complexity Cyclomatic, cognitive, nesting, declaration size
duplication Exact and near clones
dead-code Unreachable or unreferenced
documentation Docstring and comment coverage
structure File size, coupling, cohesion, dependency shape
testing Test presence, coverage, mutation
style Naming and convention conformance
types Type coverage and soundness
metrics Maintainability index, Halstead, raw counts
all Every concern (the default)

The vocabulary is the scoring model's, so an answer maps onto aspects that exist and can move a score.

It cannot come from the upstream data. Those tags are languages, ecosystems and frameworks — rails, nodejs, spring — and only 5 of the 448 eligible tools carry an upstream tag that names one of this project's concerns. What a tool measures is knowable only by running it, so measures is populated exactly as fast as adapters are written; the upstream tags are kept separately as upstream_tags.

python tools/resolve_pool.py --concerns duplication,dead-code
python tools/resolve_pool.py --concerns documentation

Depth

Depth Tools What it means
baseline 10 Multi-language or zero-config tools, one install, seconds to run
moderate 18 Baseline plus the mainstream per-language linters
heavy 18 Moderate plus slower or configuration-hungry tools; none are added today
all up to 448 Every eligible tool that speaks a detected language

Analyzers never take their configuration from the tree they audit. Decision 9 draws the line at executing code, and configuration is code: pylint's init-hook= runs arbitrary Python before analysis begins, a mypy plugins = imports a module out of the repository, and an eslint flat config is a JavaScript program. So pylint is spawned with --rcfile, mypy with --config-file, and eslint — which cannot run at all without the tree's config — is refused by selection and reported as refused rather than run. The analyzer child also gets an environment with PYTHONPATH, NODE_PATH, NODE_OPTIONS, PYTHONSTARTUP and the LD_/DYLD_ pair removed, so nothing outside the operator's choice can decide what it loads. This is not a sandbox and is not claimed as one; the child is an ordinary local process.

The two isolation flags are structurally checked everywhere and spawned only where the binary exists — the same disclosure the two JVM tools carry below. test_every_declared_tool_is_classified_for_tree_configuration forces every declared tool to be isolated or to state that it reads no tree configuration, and the live tests plant a hostile pylintrc and mypy.ini and assert the tree's code never runs. Those live tests skip wherever the tool is absent, including this project's CI, so on a machine without pylint and mypy the flags are documented-and-asserted rather than demonstrated.

A tier below all is a promise that the tool works. Nothing is placed in baseline, moderate or heavy until this project has installed it, run it, and parsed its output — with two disclosed exceptions: Checkstyle and SpotBugs are verified against recorded real output (their conditional live tests skip wherever no binary exists, including this project's CI), so their live spawn is unproven until someone supplies the binaries. PMD has run live. That is why these numbers are small and why they grow one verified tool at a time.

all is the honest name for the rest: known to exist, license recorded, invocation not yet implemented. Selecting all today runs the same tools as heavy and reports the remainder as unavailable-no-adapter. It is not a lie by omission — the report says exactly which of them ran.

License policy

Policy Adds Eligible tools
permissive MIT, BSD, Apache, ISC, Zlib… 368
copyleft-weak + LGPL, MPL, EPL 403
copyleft-any + GPL, AGPL 448
commercial-free-tier + proprietary tools with a free or OSS plan 474
unverified + tools whose license could not be confirmed opt-in, never a default

source-available and paid proprietary tools are cataloged but never selected. Adding them needs a deliberate configuration entry naming the tool, not a policy tier.

Sonar-family tools are cataloged and unadapted, not blanket-classified as commercial. The catalog classifies SonarAnalyzer.CSharp, SonarQube for IDE and SonarQube Server as weak-copyleft, while SonarQube Cloud is proprietary-free-tier; none is runnable until an adapter ships.

How the selection is made

Read from configuration (analyzers.depth, analyzers.license_policy, analyzers.concerns). A first run with no config asks the same setup questions on chat, MCP, and a CLI TTY (first run) — whether to run the pool, depth, license policy, and scan-history recording — and persists them through the same apply_answers, so both surfaces write a byte-identical maintainability-agent.json with an explicit analyzers.run. Every later run reads that file, so the pool never changes between runs of the same tree (promise P1). A non-TTY run — CI — never asks and never writes, because an audit that stopped to ask a question in a pipeline would be a broken audit, not because the CLI has a shorter questionnaire. Preview a pool without editing the file with tools/resolve_pool.py --depth / --license-policy.

The chosen depth and policy are recorded in the report, because a score computed from four tools and a score computed from forty are not the same measurement and must not be silently comparable.

Runtime prerequisites

Analyzers live in ecosystems, and the agent runs the ones it can reach. Nothing here is required — a missing runtime costs coverage, never a failed run — but each absence is reported with what would close it.

Runtime Needed for Install
Python 3.11+ The agent itself, and lizard, radon, ruff, vulture, complexipy, interrogate, pydocstyle, multimetric, pylint, mypy ships with the package
Node.js 18+ jscpd (multi-language clone detection) and the JS/TS toolchain. Not eslint: it is refused, see above brew install node / your platform's package manager
JDK 17+ PMD, Checkstyle, and SpotBugs brew install openjdk; install PMD with brew install pmd or its upstream distribution; install Checkstyle with brew install checkstyle; install SpotBugs with brew install spotbugs. SpotBugs reads bytecode that already exists (target/classes, build/classes); the agent never runs the build.
Go toolchain golangci-lint (no adapter yet) brew install go
Rust toolchain clippy (no adapter yet) brew install rust

Node tools are fetched only if you opt in. With analyzers.acquire_tools set, a missing jscpd is invoked through npx --yes, which downloads the package — a network action P1 discloses, with the fetched version recorded in the report. By default it is off: a missing Node tool is reported not-installed in coverage and appears in the environment work order with its install command, and nothing is fetched. This agent does not transmit your code in either mode.

Child tools are not network-sandboxed. Once lizard, jscpd, or any other adapter runs, this process does not police outbound connections. Install tools yourself and keep them off npx if you need an air-gapped analysis.

To avoid the fetch entirely — in an air-gapped build, or to pin a version — install the tool ahead of time and it will be used directly:

npm install -g jscpd@5

Adapter status, stated plainly

18 tools have adapters. An inventory entry without an adapter is not a shipped capability.

Verified Depth Concern Evidence
lizard baseline Complexity, NLOC, params, tokens 205 functions in src/; C/C++/C#/Java/Fortran/Go/Rust in one pass
radon baseline Maintainability Index, CC, Halstead 34 files; lowest MI 42.23
ruff baseline ~800 lint rules The repo's own linter
jscpd baseline Copy-paste clones 30-line Java clone across two files
vulture baseline Dead code 0 at ≥80% confidence
complexipy baseline Cognitive complexity history_section = 14
interrogate baseline Docstring coverage installed, run
pydocstyle baseline Docstring conventions installed, run
fortitude baseline Fortran lint, 100+ rules version 0.9.2; 8 findings on an 11-line legacy module, including C191 unreachable code
multimetric baseline Multi-language metrics version 2.4.4; contributed in the checked-in analyzer corpus
pylint moderate Design smells 107 messages on src/
mypy moderate Type diagnostics Generic diagnostic output
eslint moderate JS/TS complexity, depth, params refused since D39 — it cannot run without executing the tree's flat config. The adapter is kept, not deleted: it becomes usable the day the tool can be invoked without that config
flake8 moderate Style, mccabe complexity, unused code parses its default path:row:col: CODE message lines
cohesion moderate Per-class cohesion percentage parses its verbose class report; measurements, not gates
pmd moderate Java cognitive and cyclomatic complexity verdicts pmd check with a pinned local ruleset; SARIF output; install with brew install pmd, verify with pmd --version
checkstyle moderate Java style and documentation verdicts — the bundled Google ruleset is a convention and Javadoc guide, not a complexity or structure source checkstyle over explicit .java targets with the bundled google_checks.xml from a neutral working directory (the audited tree's optional suppressions file is never read); XML via the shared checkstyle parser; install with brew install checkstyle, verify with checkstyle --version
spotbugs moderate Java bytecode STYLE findings only — a bug-pattern tool, not a complexity or structure source; SECURITY detectors are omitted (secure-code-agent) spotbugs -textui -exitcode over existing target/classes / build/classes (or analyzers.class_dirs); BugCollection XML, never the Checkstyle parser; missing bytecode is not-applicable with a build-then-rerun work-order item, never not-installed; coverage records source vs class mtimes (ADR 012). Install with brew install spotbugs, verify with spotbugs -version. Source-read and artifact-read JVM adapters compose in one report (tests/test_d15_composition.py).

The full inventory, including everything without an adapter, is in the JSON. Additional adapters remain incremental work; the inventory alone does not make a tool runnable.

Regenerating

pip install -e ".[dev]"   # PyYAML: the upstream catalog is YAML; not a runtime dep
git clone https://github.com/analysis-tools-dev/static-analysis /tmp/sa
python tools/build_catalog.py /tmp/sa

The pinned commit in tools/build_catalog.py is what the checked-in JSON was built from. Bumping it is a deliberate act: tool licenses and deprecations change, and a silent refresh would move the pool under a score without anyone deciding to.