maintainability-agent docs
Decisions

ADR 007: Adopt the five-pillar framework, and separate practice maturity from code condition

Context

An internal framework document defines code quality across five pillars, gives each a 1–5 maturity rubric, a cost/risk table and a standards checklist, and adds a Risk × Effort prioritization matrix and a per-stack tooling template.

Two things make it worth adopting rather than noting.

It has a concept this tool lacks, and that concept is exactly the one whose absence caused this week's central bug. Its 1–5 scale measures process maturity, not code properties:

Level Meaning
1 Initial Ad hoc; problems found reactively, often in production
2 Managed Practices exist but are inconsistently applied or enforced
3 Defined Standards documented and enforced automatically in CI
4 Quantitatively Managed Metrics tracked over time and used to guide decisions
5 Optimizing Continuous data-driven improvement; near best-practice outcomes

Score the hello-world repository against that rubric and it lands at Level 1: no enforced linter, no CI gate, no tracked metrics, no coverage threshold. It cannot reach Level 3 no matter how few findings it has, because Level 3 asks whether a standard is enforced, not whether a scan came back empty.

This tool gave the same repository 5.0 / A+. The framework's rubric would have caught what four days of internal audits did not, because it measures a different thing.

It also names the audience problem this tool has never solved. The cost/risk tables translate technical neglect into velocity loss, incident cost and rework cost; the Risk × Effort matrix sequences remediation into Quick Wins, Major Projects, Fill-Ins and Reconsider. That matrix is a direct answer to the failure mode this project keeps hitting — remediation prompts that generate endless low-value nits because nothing ranks findings by consequence against effort.

Where the two models already agree

The tool's five scoring categories are the ISO/IEC 25010 sub-characteristics of maintainability: modularity, reusability, analyzability, modifiability, testability. The framework's Maintainability pillar is defined in the same terms — modularity, low coupling, low duplication, clear architecture.

So the tool is a deep decomposition of one pillar, with partial reach into two others. That is a clean relationship, not a conflict, and stating it prevents the two 1–5 scales from being mistaken for each other.

Where they conflict

Decision

1. The five pillars become the top-level reporting taxonomy, with declared scope

Pillar This agent's position
Readability Partial. Linter conformance, docstring coverage, declaration size, naming conventions where analyzers report them. Reported, with gaps named.
Maintainability Owned. The existing ISO 25010 decomposition is the detail view of this pillar.
Efficiency & Scalability Out of scope, permanently. Requires profiling, load testing and runtime telemetry. Reported as NotApplicable with that reason — an explicit statement, not a silent omission.
Security Delegated to secure-code-agent, which every audit runs (amended 2026-09-13, D177). Reports that tool's measurement; when no trustworthy measurement comes back, the entry names the reason, so a reader never mistakes silence for safety.
Testability Partial. Test presence, declaration size and policy gates today; coverage and mutation results when the operator supplies them.

Declaring scope per pillar is itself a fix. Before this the tool was silent about efficiency and security, and silence reads as "fine".

Amended 2026-09-10: delegation resolves when the delegate reports. NotApplicable was the right answer while the other half did not exist, and it was never meant to be the permanent one — the pillar is delegated, not abandoned, and the point of naming the delegate was to make the handoff sayable. secure-code-agent 0.5.0 emits security-pillar.json against this document's own vocabulary: the scope words, the two-axis split and the posture matrix with its thresholds. Two tools reporting "level 3" or "healthy" about one repository have to mean the same thing by it, or the joined view is worse than either alone.

So the entry now carries the delegate's measurement where one exists. Three properties hold, and each is how the join could go quietly wrong:

Amended 2026-09-13: the audit runs the delegate (D177). The handoff above left the pillar measured only when some other process had written the document first — CI did, and nothing else did — so every audit through the chat door reported security unmeasured. The pillar is complete, so the audit completes it:

§2's prohibition is unaffected: the two axes arrive from another process and are still never averaged.

2. Every pillar reports two independent values that are never averaged together

Practice level (1–5) — the framework's maturity rubric, scored from detectable evidence of enforcement: does a linter config exist, is it wired into CI, is there a coverage gate, are complexity thresholds configured, are ADRs maintained, is there a duplication check. This is measured by looking at the repository's configuration and CI, not its source. Configuration is read wherever it lives — a gate in a nested api/pyproject.toml, a docs/decisions/ folder not named adr* — bounded by the same exclusion the scan uses so a vendored manifest does not count (plan-81dc6870 Class 3).

Code condition — what the analyzers found, normalized over population, as the tool scores today. For a pillar this tool owns, it is the mean of the pillar's measured aspects.

Amended 2026-09-21 (D206): the condition states what produced it. The mean is taken over the aspects that carried a number, which is the only honest arithmetic — a None is not a zero — and it means an unmeasured aspect is dropped rather than counted. Maintainability's seven aspects with one bad one average 4.43; withhold that one aspect and the same pillar reads 5.0. That is P3's shape, withholding evidence improving a reported value, and for eleven months both printed as a bare number beside the same declared list of seven.

Flooring it, as the overall grade is floored, is rejected. _grade_on_the_floor prices unknowns at 0 so concealment is monotonically unprofitable, and that is right for the graded field. A pillar's condition is not that field: it is defined here as the mean of what was measured, and a delegated pillar's condition arrives already computed by the tool that owns it. Flooring one side would put two incomparable numbers in the same column, which is a worse lie than the one being fixed.

So the defence is disclosure. Every entry carries condition_coverage — which aspects were measured, which could not be, and which had no population to measure — and no skin prints a partial condition without saying how many aspects made it and which are missing. "Could not measure" and "nothing to measure" are named separately, because collapsing them is how an unknown reads as clean.

The two are orthogonal and both are needed:

Poor condition Good condition
High practice Known debt, managed — invest in remediation Healthy
Low practice Unmanaged debt — highest risk Unverified. Clean scan, no enforcement, nothing preventing regression tomorrow

The hello-world sits in the bottom-right cell. Today the tool calls that A+. Under this decision it reads "practice level 1, condition unmeasured", which is the truth.

Combining them into one number is forbidden. They answer different questions and the framework's own document keeps them apart.

3. Findings are prioritized by Risk × Effort, and the prompt leads with Quick Wins

Every finding class carries a risk weighting (its cost driver, from the framework's cost/risk tables) and an effort estimate. The remediation prompt orders by that matrix rather than by count or severity alone:

This is the structural answer to nit-loops: a prompt that opens with eighty line-length violations is generating Fill-Ins as if they were Quick Wins.

4. Shared vocabulary is adopted verbatim

The framework's glossary — cyclomatic complexity, duplication %, code churn, bus factor, coverage % — becomes the tool's published vocabulary. The tool already measures churn and a bus-factor proxy under the name knowledge_concentration; that aspect is renamed or aliased to the shared term so a report and the framework can be read side by side.

Amended 2026-08-13: the rename is refused, and the mapping is published instead. knowledge_concentration is the share of settled files (3+ commits) that exactly one person has touched. Bus factor is a count of people whose loss would stall the project. A repository where one author owns 80% of files can still have a bus factor of four, so these are different quantities and the shared term would claim a measurement this tool never makes — the defect the rest of this record exists to prevent. The glossary reconciliation lives in standard.md, which states the relationship for every term including the two the tool does not measure at all. test_the_ownership_aspect_does_not_claim_to_be_bus_factor fails the build if someone closes this item by renaming the key.

The framework's action thresholds (below 2.5 urgent, 2.5–3.5 targeted, above 3.5 maintain) apply to practice level, where they were designed to apply. They are not applied to the condition scale, whose bands are separately calibrated.

Options considered

A. Ignore the framework; keep ISO 25010 only. Rejected. It forfeits the practice-maturity concept, which demonstrably catches a bug the current model cannot see, and leaves the tool speaking a vocabulary no stakeholder uses.

B. Replace ISO 25010 with the five pillars. Rejected. Two pillars are unmeasurable or delegated, and the ISO decomposition is the tool's calibrated, tested detail. Replacement would discard working machinery for a taxonomy that does not decompose far enough to score.

C. Adopt the pillars as the outer taxonomy, keep ISO 25010 as the Maintainability detail, and add practice level as a second axis. Accepted. Nothing calibrated is discarded, the tool gains the missing concept, and the two 1–5 scales stay explicitly separate.

D. Merge practice and condition into one composite score. Rejected. It is the mistake that makes both meaningless, and it would reintroduce exactly the confusion this record exists to prevent.

Consequences

Invariants

  1. Every pillar appears in every report, with an explicit scope status: owned, partial, delegated or out of scope.
  2. Practice level and code condition are separate fields and are never averaged into a single number.
  3. A repository with no enforcement evidence cannot report a practice level above 2, regardless of findings.
  4. Delegated and out-of-scope pillars carry a reason naming what would be required, so silence is never read as a pass.
  5. Remediation output is ordered by the Risk × Effort matrix, and Fill-Ins never appear above Quick Wins.
  6. The framework's action thresholds are applied only to practice level, never to the condition scale.