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:
- run the validated analyzer pool: yes;
- analyzer depth: moderate;
- license policy: permissive;
- economic context: skip;
- run this repository's test suite for coverage — this executes the tree: no;
- analyzer results your own build or pipeline already writes as SARIF: none;
- presentation: chat, markdown, html or json — default chat.
jsonis the report dictionary itself, for a pipeline rather than a reader (D201); - record scan history in this repository: yes.
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:
- loaded labor rate, lower bound — default 90 per hour;
- loaded labor rate, central estimate — default 140 per hour;
- loaded labor rate, upper bound — default 210 per hour.
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 exact test command to run, e.g.
pytest -qornpm test.
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.
History consent and files
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.