maintainability-agent docs
Help

First run and questions

Chat, MCP, and an interactive CLI TTY are one setup: the same questions. MCP is the chat transport, not a third questionnaire. There is not a CLI process, a chat process, and an MCP process. The list below is the set. A surface that asks a subset is a bug.

CI, a non-TTY, and an explicit --config never ask. That is not a different questionnaire; there is no operator. MCP allowed-root grants (this session / always / no) are a server boundary, not these questions.

Call audit_repository on the chat path, or run the CLI at a TTY with no config. The tool checks configuration; the host does not inspect the repository first. Unset MCP action never audits.

If both the repository's maintainability-agent.json and the XDG user configuration are absent, the interactive surface asks the setup questions and does not audit — no report, no score, no grade. On MCP that is setup_needed with audit_ran: false. A host that can elicit asks those fields as one structured form with visible defaults:

The labor rates are a second question set, asked only if you include the economic scenario. Answer include and the next call asks three more:

Answer skip — the default — and you are never asked. Until this was staged, all three were on the first form regardless, so the common path answered three money questions for something it had just declined.

They are refused if they do not satisfy 0 < low <= base <= high. That check used to live in scoring, so an impossible set was accepted here, written to both tiers, and surfaced two calls later as an error about a file the person had already left behind.

Your pipeline's SARIF files are a second question, asked only if you answer declare (Decision 17). This tool runs a pinned handful of the analyzers it catalogs; the rest run where you already run them — a .NET build runs the Roslyn analyzers — and this reads what they wrote, never running them. The next call asks for each file and the concerns it covers, e.g. artifacts/roslyn.sarif: style, documentation, dead-code, files separated by ;, paths relative to the repository. They are stored in analyzers.pipeline_results. A declared file turns its concerns from unexamined to examined for the languages its results name, its findings are listed, a file older than the last commit is called stale, and nothing in it moves the grade. A blank answer cancels. Answer none — the default — and you are never asked.

The test command is a second question set, asked only if you opt the suite in. Answer yes to running the suite and the next call asks one more:

The question arrives pre-filled where the repository answers it. Since 2.5.0 the tool reads the tree's build manifests — Package.swift, fpm.toml, Cargo.toml, go.mod, CMakeLists.txt, pom.xml, build.gradle, a .sln, package.json, or a declared [tool.pytest] section — and offers the command they imply, naming the file it read. Accept it, edit it, or replace it; whatever you submit is what gets stored. It is only ever a default. The require_test_command hard gate asks whether you documented a command, so a suggestion the tool made and you never answered does not satisfy it.

An Xcode project with no Package.swift is left blank on purpose: bare xcodebuild test needs a -scheme and usually a -destination, and neither can be read off the tree, so there is no command to offer that would run.

Clearing the line still cancels the opt-in, exactly as a blank answer always did.

This is the one exception to the tool never running the audited tree (Decision 9, amended 2026-08-31): it executes only the command you name, only because you opted in, and only for this repository. A blank answer declines for this repository. The command is documented in the repository's expected_commands.test and your consent is recorded against this repository in your user configuration; a repository with no consent of its own is asked its command, and nothing runs until it has one. Its line coverage, if the run produces a coverage.xml, coverage/lcov.info or lcov.info, scores test_effectiveness as coverage / 20, and the aspect is NotApplicable on every run that does not opt in. Answer no — the default — and neither this question nor any execution happens.

Accepted answers are written to both configuration tiers. Answering does not start an audit, including when the host elicited the questions on that call. The next unset call on the now-configured repository returns choice_needed with options run and reconfigure, also audit_ran: false. action="run" audits. action="reconfigure" reopens the setup questions on a repository that already has answers.

A decline or a host without elicitation support receives setup_needed with the same choices and does not receive a report; a later call asks again until answers are written. An explicit config_path bypasses both gates so automation is not blocked. The MCP tool passes unset action because a person is on the other end. The plain audit_repository Python function defaults action="run" for the CLI, the report resource, and scripted callers that have already decided.

Repository grants

An audit-tool call outside the server's allowed roots uses one structured grant question when the host supports it: this session (the default), always, or no. A session grant lasts only for the running process. Always writes the root to the user configuration, never the repository configuration. No—or a host without elicitation—returns the boundary error with the --allow-root and MAINTAINABILITY_MCP_ALLOWED_ROOTS remedies. Report-resource reads never ask for or persist a grant. How that boundary text survives MCP is the entry-layer transport rule in ../architecture.md: only the transport declares the named anticipated refusals that may carry their text to the caller.

Written history.record consent wins over terminal interactivity. With no written answer, a CLI TTY may start a series; an existing history file remains a standing answer and appends. Explicit per-call true or false still wins.

Chat returns the bounded view. If the user chooses a file presentation — markdown or html — the host asks where to save it and passes that location as output_path; MA saves the complete report there and returns the bounded view and report_path. A file format with no location is refused before the audit runs. Nothing writes or saves a report file without that chosen location.