Skip to main content

Maintainability Agent

maintainability-agent — a deterministic audit whose output is a bounded work order for your AI coding agent: fix exactly these findings, refactor nothing else

A deterministic, offline maintainability audit whose output is a bounded work order for an AI coding agent — a copy-paste prompt, per finding, that says fix exactly these and refactor nothing else. Chat-primary; CLI for CI. Version 1.9.0.

Languages parsed: Python, Java, C, C++, C#, Fortran (free-form and fixed-form), and the JS/TS family — each by a scanner written for it, and measured with that language's own reading of what a branch is. What that means per language.

pip install maintainability-agent          # CLI + library
pip install "maintainability-agent[mcp]"   # optional local MCP server for chat / IDE hosts
cp -r skills/maintainability-agent ~/.claude/skills/   # Claude Code slash command

Executive summary

Agents write code faster than anyone can review it. Not measurably worse code — this project tested that claim about itself and retracted it — just more, arriving faster than trust can accumulate. Linters and quality dashboards catch some of the resulting slop. None of them ship the one thing that actually closes the loop: a bounded prompt back to the agent, scoped to the findings, that forbids unbounded rewrites.

That prompt is the product. Everything else — the scanner, the ISO/IEC 25010-inspired 0–5 score, the analyzer pool, the semantic and economic signals — exists to aim it. Remove the prompt and what's left is a worse version of tools that already ship.

What 1.0 guarantees:

  • Deterministic and local. Same tree, config, pinned analyzer versions and history in → same evidence, findings and score out. The analysis performs no network access and invokes no LLM, and this agent does not transmit your source. Third-party analyzers it may spawn (eslint, jscpd, lizard) are not network-sandboxed — this process does not police whether they phone home; install them yourself for an air-gapped run.
  • One uniform rubric, readable in source, applied to every repository — so "better" and "worse" are not an argument. Calibrated against a query-selected corpus of mature open-source projects; the corpus median earns a B, and A+ is gated, not averaged.
  • Honest about evidence. A score is withheld when too little was examined to support one — a --changed-only diff is not a repository grade, and a shallow clone is not an A. Every reported value names what measured it.
  • The complete work order is the report; chat/CLI is a bounded UI. The HTML/Markdown report carries the entire backlog with a deterministic copy-paste prompt for each item. The chat surface stays a tight summary so a host's payload cap can never truncate the prompt.
  • One setup, three transports. Chat, MCP, and an interactive CLI TTY ask the same first-run questions. A surface that asks a subset is a bug.

Governing intent lives in docs/product-intent.md — that document is authoritative, and this README defers to it wherever the two differ.

Why this exists

The ratio of code-written to code-reviewed has collapsed. Unmaintainable code that used to accumulate over years can now accumulate in an afternoon: duplicated helpers, oversized files, speculative abstractions — the same slop hand-written codebases always accrued, now at machine speed.

The same speed is the way out. An agent pointed at specific, deterministic findings can fix them at the rate they appear. The loop this tool closes:

  1. Measure pressure points deterministically, with no LLM involved.
  2. Score with one uniform standard, so the verdict is not a debate.
  3. Emit a prompt scoped to those findings only, with explicit instructions not to refactor beyond them.
  4. Hand it to the agent. Review a scoped diff instead of a speculative rewrite.

Step 3 is the product; steps 1 and 2 are in service of it. Every other tool in this space stops at "here's a list of findings."

One pre-registered experiment has tested the bounded prompt. Generic prompting made 2 of 6 repositories worse; bounded prompting made 1 of 6 worse and improved 5 of 6, under this tool's own finding count. The registered hypothesis was narrower diffs, which did not hold, so the registered verdict stands at INCONCLUSIVE. Method, limits and raw data: docs/studies.md.

Who it's for

  • Teams running AI agents in the dev loop, tired of unbounded cleanup PRs, who want a CI gate that actively constrains follow-up scope.
  • Repos that want a maintainability gate without a SaaS analyzer or shipping code to a third party.
  • Solo devs who want a single deterministic audit to pin in a Makefile, pre-commit, or local CI script.

The road to 1.0

1.0 is not a rewrite. It is the line drawn under a long arc of subtraction — what remained after every claim the project could not stand behind was removed. The arc is the credibility:

0.5.0 — the scoring engine was rebuilt. The old model counted findings absolutely, so it graded repo size, not maintainability: it scored Django, pytest, black, tornado, httpx, lodash, svelte, fastapi all at 0.0 / F while a 53-file toy repo scored 4.6 / A. Scores became rates, normalized per dimension against what real code carries and calibrated so the corpus median earns a B. See docs/standard.md.

0.6.0 — a near-duplication finding that pairs the copies, naming the declaration to reuse (toAtomicAmount at TradeTicket.tsx:862 already does this), so renaming can't hide clone-instead-of-reuse. Useful on its own terms — and explicitly not evidence about who wrote the code.

Retracted: that near-duplication distinguishes AI-written code. 0.6.0 had called it "the first signal that separates AI-written applications from mature human-written OSS." Re-run against a control matched on age, popularity and language, the near-duplication gap is not significant (p = 0.546), and no other metric earns the claim either. The honest summary is this design could not measure a difference, not there is no difference. See docs/studies.md.

0.7.0 — the evidence model. The score is withheld when the evidence cannot support one; a diff is not a repository grade. External analyzers became the primary evidence where they measured a full concept set, with the built-in detectors as the fallback and disagreement widening the range rather than being averaged. The grade is gated and banded from the evidence floor, so withholding evidence can never buy a better letter.

0.8–0.9 — one setup, chat-first. Chat / MCP became the primary surface, the CLI the automation door, and the three transports converged on a single question set. A run of chat-surface wiring defects — and their closing tests — is recorded in docs/defect-register-chat-surface.md.

1.0.0 — acceptance and the complete work order. A real-repo acceptance round on a mixed Python/TypeScript codebase hardened the last edges: the report now carries the entire backlog with a per-item copy-paste prompt, charts were rebuilt for legibility, reconfigure stopped destroying hand-tuned config, and TypeScript semantic coverage learned to find workspace projects and a locally-installed compiler. See the changelog.

The through-line: this repository runs the tool against itself in CI and checks the report in (docs/self-audit.md). An earlier revision of the self-audit table advertised 5.0/A+ after the codebase had drifted to a B; a hostile audit caught the stale claim — precisely the failure mode this tool exists to catch.

Install

python3 -m pip install maintainability-agent
maintainability-agent --root . --config maintainability-agent.json

Or run from a source checkout without installing:

python3 -m maintainability_audit --root . --config maintainability-agent.json

For an editable dev install and the full local-verification sequence, see CONTRIBUTING.md. Upgrading from 0.x? The scale and the evidence model changed on the way to 1.0 — see docs/migration-1.0.md.

Primary Surface: Chat / MCP

Drive the local MCP process from an IDE assistant or chat host. Call audit_repository; unset action never audits. An unconfigured repository (no repository maintainability-agent.json and no user config) returns setup_needed and audit_ran: false — structured setup choices for analyzer policy, history consent, economics, test-suite execution, and presentation, and no report. A configured repository returns choice_needed (run or reconfigure), also without a report. Answering setup does not start an audit. action="run" returns the report and its bounded remediation prompt to the conversation. record_history=None follows the persisted first-run history consent and always appends to an existing history, while an explicit true or false wins.

python3 -m pip install "maintainability-agent[mcp]"
maintainability-agent mcp --allow-root /absolute/path/to/repository

Presentation is exactly three choices — chat, a Markdown file, or a single-file HTML report — and where to save a file is asked only after a file format is chosen. No report file is written without that choice. See chat workflow help and IDE and agent integration.

Automation / CI: CLI

Use the CLI for scripts, repeatable automation, and CI gates. Copy the example config to your repo root as maintainability-agent.json, then:

maintainability-agent \
  --config maintainability-agent.json \
  --fail-on-gate \
  --output maintainability-report.md \
  --prompt-output maintainability-remediation-prompt.md \
  --comment-output maintainability-pr-comment.md \
  --sarif-output maintainability.sarif

--fail-on-gate fails CI on hard gates only (a missing README, an undocumented test command, a breached threshold) — never on a letter grade, and never on a withheld estimate. For PR work, add --changed-only main...HEAD to audit the diff; on a change too small to support a rate, the estimate and grade are withheld and the scope is named as the reason.

What it analyzes

The deterministic scanner reads code from your repo (no LLM calls) and produces signals on:

  • largest files (configurable warn/fail thresholds)
  • function size and complexity — exact ranges for Python via ast, brace-bounded for JS/TS/JSX/TSX/HTML — plus cognitive complexity (nesting-weighted reading cost)
  • class size, against its own budget (max_class_lines)
  • duplicate blocks, and near-duplicate declarations compared structurally so renaming can't hide a copy, each paired with the original to reuse
  • unreferenced private declarations (debris nothing can reach)
  • competing libraries for one concern (two HTTP clients, two validators)
  • configurable risk patterns (eval(, exec(, TODO/FIXME, custom regex)
  • expected files / commands / clean-worktree (opt-in hard gates)
  • TypeScript semantic facts from a recorded analysis or an already-installed tsc, including workspace projects (ADR 003)
  • test effectiveness from an opted-in suite run and parsed coverage (Class 5, default off — the one place the agent may execute the tree)
  • an ISO/IEC 25010-inspired 0–5 estimate per category, and a verified grade — or a disclosed withholding when the evidence is thin.

The analyzer is intentionally conservative and under-reports rather than over-reports: an unrecognized declaration costs one missed finding, never a cascade of false ones. Pair it with native tools (ESLint, Ruff, Radon, Semgrep, SonarQube, Qlty) rather than replacing them — their SARIF folds in via --sarif-input. Accuracy and limits: docs/language-support.md.

Language support

Be clear-eyed about this: the tool does not support every language equally, and on an unrecognized language it will quietly under-report rather than fail. Coverage comes from two layers.

Eight languages are parsed as of 1.6.0: Python (1.0), Java (1.0), C (1.1), C++ (1.2), C# (1.3), Fortran (free-form 1.4, fixed-form 1.6), the JS/TS family, and HTML. Each has a scanner written for it and a documented list of what it misses — a language is claimed here only when both exist.

Built-in scanner (always on, no dependencies) — reads function/class declarations, sizes and complexity for a fixed set of languages, and only these:

Language How it's measured Fidelity
Python (.py) ast — exact end_lineno Exact
Java (.java) dedicated brace-bounded scanner Bounded; under-reports some constructs
C (.c, .h) dedicated brace-bounded scanner — functions, struct/enum/union Bounded; prototypes and macros are not declarations
C++ (.cpp, .hpp, .cc, .cxx, .hh) dedicated brace-bounded scanner — functions, class members, namespaces, templates Bounded; bodyless declarations are not definitions
C# (.cs) dedicated brace-bounded scanner — methods, constructors, class/interface/struct/record/enum Bounded; properties are not declarations
Fortran, free-form (.f90, .f95, .f03, .f08, .F90, .F95, .F03, .F08, .pf) dedicated keyword-bounded scanner — modules, subroutines, functions, derived types Bounded by end; measured with Fortran's own branch and nesting reading
Fortran, fixed-form (.f, .for, .ftn, .F, .FOR, .FTN) the same scanner over card-column source; continuations joined, labelled DO loops understood Bounded by end or by the loop's label
JS / TS / JSX / TSX (.js, .jsx, .mjs, .cjs, .ts, .tsx) brace/paren depth over a masked copy Bounded by the declaration's own braces
HTML (.html) same brace scanner (inline <script>) Bounded
TypeScript (semantic) a recorded analysis or a locally-installed tsc, workspace projects included Type-level facts; unknown when no checker is present

Any language not in that table — Go, Rust, Ruby, PHP, Kotlin, Swift, and the rest — is not parsed for declarations by the built-in scanner. Its files still count toward repo size, but the built-ins produce no function-size, complexity, duplication or dead-code findings for them, and the estimate leans on whatever evidence is available (which is why the report discloses its evidence tier and can withhold the grade).

Which analyzer covers which language (the opt-in pool, when installed):

Language Built-in scanner External analyzer
Python ast, exact ruff, radon, mypy, vulture, complexipy, interrogate, pydocstyle, pylint, cohesion
Java dedicated scanner lizard, PMD, Checkstyle, SpotBugs
C / C++ / C# dedicated scanners lizard, multimetric
Fortran (free- and fixed-form) dedicated scanner fortitude — 100+ rules; lizard — complexity, NLOC, params
JS / TS / JSX / TSX brace scanner ESLint, lizard, jscpd

Fortran reached parity in 1.6.0. lizard measures it — this project's catalog row for lizard was stale and said otherwise, so lizard came out not-applicable on every Fortran repository and never ran — and fortitude adds 100+ lint rules beside it. A lint now fails the build if any parsed language has no analyzer that measures complexity.

External analyzer adapters (opt-in pool) — this is how coverage extends beyond the built-in set. When you enable the analyzer pool, the tool shells out to mature analyzers and folds their output in through per-tool adapters: lizard (cyclomatic complexity across ~a dozen languages), jscpd (cross-language duplication), ESLint (JS/TS), PMD / SpotBugs (JVM), and others in the catalog. These run only when selected and installed (acquisition is opt-in and off by default), and where they measured a full concept set they become the primary evidence, with the built-ins as the fallback.

So: first-class today is Python (and TypeScript for semantics); Java, C, C++, C#, free-form Fortran and the JS/TS family are bounded-but-real; every other language is only as covered as the external analyzer you point at it — and with no analyzer, it is under-reported by design. The per-language accuracy, the exact parsed set, and the known limitations are documented in docs/language-support.md; the adapter catalog is in docs/adapters.md.

What it produces

Any combination of: maintainability-report.md (or a single-file HTML report), maintainability-remediation-prompt.md (the bounded prompt), maintainability-pr-comment.md, maintainability.sarif (2.1.0, for GitHub Code Scanning), maintainability-baseline.json (for --fail-on-new incremental adoption), and per-tool agent instruction files (AGENTS.md, CLAUDE.md, Cursor/Copilot/Windsurf rules) via --init-agent-standards.

The report is the complete work order. The HTML and Markdown reports carry the whole backlog — every finding with a self-contained, deterministic copy-paste prompt telling the agent to fix only the listed items, keep the patch small, preserve architecture and behavior, add tests where behavior changes, and report false positives instead of rewriting blindly. Chat and CLI render a bounded view — a summary plus the top items and a pointer to the report — so a payload cap can never truncate the prompt.

Scoring standard

Based on ISO/IEC 25010 maintainability — modularity, reusability, analyzability, modifiability, testability. Scores are rates calibrated against real code, not counts: every pressure is normalized against the median a pinned 40-repo corpus of mature projects (django, angular, transformers, webpack, vite, playwright, …) actually carries, so 2.5x means "two and a half times what well-maintained real code shows." The corpus median earns a B; A+ is gated, requiring every dimension clean.

The corpus is selected by query, not tastestars:>3000 created:<2021-01-01 pushed:>2026-01-01 across Python, TypeScript and JavaScript, then filtered to repositories that contain code. The calibration is reproducible, not asserted: python3 tools/calibration/measure.py --check re-measures and fails on drift, and tests/test_calibration_corpus.py re-derives the constants offline from checked-in measurements. See docs/standard.md.

Self-audit

This repo eats its own dogfood — the tool runs against this codebase in CI, and a report is checked in at docs/self-audit.md, stamped with the exact source commit it was generated against (a provenance record, not a claim about HEAD). These figures mirror that stamped report row for row:

Metric Value
Maintainability estimate 4.1 / 5
Verified grade B
Files scanned 391
File warnings 120
File failures 0
Function warnings 65
Function failures 0
Duplicate blocks 0
Risk findings 0
Hard gate failures 0

Yes, a B — demoted from the A band because warning rates exceed the A-grade ceilings, against thresholds this repo sets stricter than the shipped defaults. The grade is gated (A+ needs every dimension clean; a repo with production code and zero test files cannot earn an A-grade) and banded from the evidence floor, so withholding evidence never buys a better letter. Every threshold gate — file, function, duplication — is opted on for this repo's own CI, against thresholds stricter than the shipped defaults, so drifting below the bar fails the build rather than the README. (CI note: actions/checkout defaults to fetch-depth: 1, which hides history and costs roughly a grade — use fetch-depth: 0.)

Platform support

POSIX. Linux and macOS are what this has run on; Windows is untested and not claimed. That is a statement about evidence, not a refusal: CI runs Linux, development runs macOS, and the test suite creates symlinks with no platform guard (needing Developer Mode on Windows), so it never reaches the point of telling anyone whether the product works there. Adding windows-latest to CI and fixing what falls out is how that changes.

Invokable skill / slash command

This repo ships a portable skill under skills/maintainability-agent/ so /maintainability-agent is one keystroke away. Keep the installed copy in sync — a drifted skill teaches agents a dead workflow:

maintainability-agent --install-skill        # writes ~/.claude/skills

Re-run after every upgrade; a differing installed copy is refused with the list of differences (--force-skill to overwrite).

Host Install destination Invocation
Codex / OpenAI via skills/maintainability-agent/agents/openai.yaml per Codex's skills convention
Claude Code skills/maintainability-agent/~/.claude/skills/maintainability-agent/ (or repo .claude/skills/) /maintainability-agent
GitHub Copilot (VS Code) skills/maintainability-agent/copilot/maintainability-agent.prompt.md<repo>/.github/prompts/ /maintainability-agent in Copilot Chat

For always-on guidance instead of an invokable skill, use --init-agent-standards (see docs/ide-agent-integration.md).

GitHub Action

This repo ships action.yml, usable as a composite action:

- uses: marshallguillory86/maintainability-agent@v1.0.0
  with:
    config: maintainability-agent.json
    changed-only: main...HEAD
    fail-on-gate: "true"

Or copy .github/workflows/maintainability.yml into the target repo. For repos not on GitHub Actions, examples/local-ci.sh enforces coverage and writes coverage.xml.

Documentation

Start with the documentation index, which states each document's genre and what it is allowed to assert.

GoverningProduct intent (authoritative: promises, and what it must never claim) · Architecture (layers, enforced invariants, known debt) · Philosophy (why AI-specific: volume, not pathology) · Decision register including ADR 001 (evidence and verification).

ReferenceMaintainability standard · Studies and measured results · Report contract · CLI reference · Config schema · Language support · Analyzer adapters · IDE and agent integration · Chat workflow help · PR and baseline workflows · Roadmap · Changelog.

Running tests

PYTHONPATH=src python3 -m pytest

The full local verification that matches CI — ruff, pip-audit, the 92% coverage gate, and the self-audit — is in CONTRIBUTING.md.

Get in touch

Support this work

This is a single-maintainer, MIT-licensed project — free to use, and built on a lot of unpaid hours. If it saves you or your team time, please consider sponsoring its continued development:

❤️ Sponsor on GitHub

Sponsorship is entirely optional and never gates a feature, a fix, or support — the whole tool stays free and open. It just helps keep the work going.

Acknowledgements

  • Miles Parker — identified the friction-signal gap: that the "this keeps fighting me" signal a maintainer accumulates over months is exactly the evidence an LLM cannot hold across sessions, and that a tool positioned between the two should carry it. A contribution of insight rather than code, and it changed what this project is for.

License

MIT

Download files

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

Source Distribution

maintainability_agent-1.9.0.tar.gz (893.0 kB view details)

Uploaded Source

Built Distribution

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

maintainability_agent-1.9.0-py3-none-any.whl (466.9 kB view details)

Uploaded Python 3

File details

Details for the file maintainability_agent-1.9.0.tar.gz.

File metadata

  • Download URL: maintainability_agent-1.9.0.tar.gz
  • Upload date:
  • Size: 893.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for maintainability_agent-1.9.0.tar.gz
Algorithm Hash digest
SHA256 1c8c66cdb0e65bc71da21ddecb596f237ffec2a8a186a3eaf938299158edbb0d
MD5 d30661a4dbf91e92f84e45b38f45d4a0
BLAKE2b-256 5497aefc0fdb37dffc68c77c840c3da1f613c980cc829b284d2c1235aa2a0d70

See more details on using hashes here.

Provenance

The following attestation bundles were made for maintainability_agent-1.9.0.tar.gz:

Publisher: release.yml on marshallguillory86/maintainability-agent

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file maintainability_agent-1.9.0-py3-none-any.whl.

File metadata

File hashes

Hashes for maintainability_agent-1.9.0-py3-none-any.whl
Algorithm Hash digest
SHA256 dbfff4af9793075916daeb42cb14db9eed852e02af8254ab447f5cc76ac15261
MD5 8ee0058254b0c1e379b323d4cf387481
BLAKE2b-256 a733a19b01b34809021d7e56dd19af0afe1588ada4e2514468db08c87744f64c

See more details on using hashes here.

Provenance

The following attestation bundles were made for maintainability_agent-1.9.0-py3-none-any.whl:

Publisher: release.yml on marshallguillory86/maintainability-agent

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

3.7.7

2 files

3.7.6

2 files

3.7.5

2 files

3.7.4

2 files

3.7.3

2 files

3.7.1

2 files

3.7.0

2 files

3.6.3

2 files

3.6.2

2 files

3.6.1

2 files

3.6.0

2 files

3.5.0

2 files

3.4.0

2 files

3.3.0

2 files

3.2.0

2 files

3.1.0

2 files

3.0.2

2 files

3.0.1

2 files

3.0.0

2 files

2.11.2

2 files

2.11.1

2 files

2.10.0

2 files

2.9.0

2 files

2.8.1

2 files

2.8.0

2 files

2.7.0

2 files

2.6.0

2 files

2.5.0

2 files

2.4.1

2 files

2.4.0

2 files

2.3.0

2 files

2.2.0

2 files

2.1.0

2 files

2.0.0

2 files

1.10.1

2 files

1.10.0

2 files

This release

1.9.0 This release

2 files

1.8.3

2 files

1.8.2

2 files

1.8.1

2 files

1.8.0

2 files

1.7.0

2 files

1.6.0

2 files

1.5.1

2 files

1.5.0

2 files

1.4.0

2 files

1.3.0

2 files

1.2.0

2 files

1.1.0

2 files

1.0.1

2 files

1.0.0

2 files

0.9.1

2 files

0.9.0

2 files

0.8.1

2 files

0.8.0

2 files

0.7.0

2 files

0.6.1

2 files

0.2.0

2 files

0.1.0

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