maintainability-agent 2.1.0__tar.gz → 2.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (312) hide show
  1. {maintainability_agent-2.1.0/src/maintainability_agent.egg-info → maintainability_agent-2.3.0}/PKG-INFO +2 -2
  2. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/README.md +1 -1
  3. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/pyproject.toml +1 -1
  4. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0/src/maintainability_agent.egg-info}/PKG-INFO +2 -2
  5. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_agent.egg-info/SOURCES.txt +5 -0
  6. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/__init__.py +1 -1
  7. maintainability_agent-2.3.0/src/maintainability_audit/_attestation.py +166 -0
  8. maintainability_agent-2.3.0/src/maintainability_audit/_gates.py +122 -0
  9. maintainability_agent-2.3.0/src/maintainability_audit/_ratchet.py +109 -0
  10. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/cli.py +26 -46
  11. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/config.py +1 -1
  12. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/prompts.py +1 -0
  13. maintainability_agent-2.3.0/tests/test_attestation.py +151 -0
  14. maintainability_agent-2.3.0/tests/test_dimension_ratchet.py +155 -0
  15. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_identity_resolution.py +5 -1
  16. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_scope_conformance.py +6 -4
  17. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/LICENSE +0 -0
  18. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/setup.cfg +0 -0
  19. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_agent.egg-info/dependency_links.txt +0 -0
  20. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_agent.egg-info/entry_points.txt +0 -0
  21. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_agent.egg-info/requires.txt +0 -0
  22. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_agent.egg-info/top_level.txt +0 -0
  23. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/__main__.py +0 -0
  24. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_adapters.py +0 -0
  25. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_analysis.py +0 -0
  26. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_aspects.py +0 -0
  27. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_assets/analyzer-catalog.json +0 -0
  28. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_assets/standard.md +0 -0
  29. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_backfill.py +0 -0
  30. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_bands.py +0 -0
  31. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_built_ins.py +0 -0
  32. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_calibration.py +0 -0
  33. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_catalog.py +0 -0
  34. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_charts.py +0 -0
  35. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_cognitive.py +0 -0
  36. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_config_defaults.py +0 -0
  37. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_conformance.py +0 -0
  38. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_corroborate.py +0 -0
  39. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_coverage_notes.py +0 -0
  40. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_derive.py +0 -0
  41. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_discovery.py +0 -0
  42. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_documents.py +0 -0
  43. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_economics.py +0 -0
  44. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_economics_view.py +0 -0
  45. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_environment.py +0 -0
  46. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_evidence_view.py +0 -0
  47. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_finding_match.py +0 -0
  48. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_first_run.py +0 -0
  49. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_formula.py +0 -0
  50. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_generic.py +0 -0
  51. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_grant_ledger.py +0 -0
  52. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_history_view.py +0 -0
  53. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_hostile_prompt.py +0 -0
  54. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_hotspots.py +0 -0
  55. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_html_report_sections.py +0 -0
  56. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_html_view.py +0 -0
  57. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_identity.py +0 -0
  58. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_jvm_adapters.py +0 -0
  59. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_markdown_sections.py +0 -0
  60. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_masking.py +0 -0
  61. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_mcp_audit.py +0 -0
  62. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_mcp_gate.py +0 -0
  63. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_mcp_grants.py +0 -0
  64. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_mcp_refusals.py +0 -0
  65. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_mcp_resources.py +0 -0
  66. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_mcp_setup.py +0 -0
  67. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_metric_adapters.py +0 -0
  68. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_metrics_types.py +0 -0
  69. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_pillars.py +0 -0
  70. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_practice.py +0 -0
  71. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_pressures.py +0 -0
  72. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_ranges_c.py +0 -0
  73. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_ranges_core.py +0 -0
  74. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_ranges_cpp.py +0 -0
  75. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_ranges_csharp.py +0 -0
  76. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_ranges_fortran.py +0 -0
  77. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_ranges_java.py +0 -0
  78. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_ranges_js.py +0 -0
  79. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_recurrence.py +0 -0
  80. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_runner.py +0 -0
  81. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_safe_write.py +0 -0
  82. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_scan_history.py +0 -0
  83. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_scan_view.py +0 -0
  84. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_second_source.py +0 -0
  85. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_selection.py +0 -0
  86. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_semantic.py +0 -0
  87. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_semantic_policy.py +0 -0
  88. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_semantic_ts.py +0 -0
  89. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_semantic_view.py +0 -0
  90. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_setup_errors.py +0 -0
  91. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_setup_persist.py +0 -0
  92. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_skill_data/SKILL.md +0 -0
  93. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_skill_data/agents/anthropic.yaml +0 -0
  94. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_skill_data/agents/copilot.yaml +0 -0
  95. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_skill_data/agents/openai.yaml +0 -0
  96. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_skill_data/copilot/maintainability-agent.prompt.md +0 -0
  97. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_skill_data/references/finding-taxonomy.md +0 -0
  98. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_skill_install.py +0 -0
  99. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_stored_grants.py +0 -0
  100. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_tdd_view.py +0 -0
  101. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_test_execution.py +0 -0
  102. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_test_pairing.py +0 -0
  103. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_tokens.py +0 -0
  104. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_tool_adapters.py +0 -0
  105. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_trends.py +0 -0
  106. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_user_config.py +0 -0
  107. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_verdict_adapters.py +0 -0
  108. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_verification.py +0 -0
  109. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_work_order.py +0 -0
  110. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_work_order_view.py +0 -0
  111. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_work_order_weights.py +0 -0
  112. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/_xml.py +0 -0
  113. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/baseline.py +0 -0
  114. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/deadcode.py +0 -0
  115. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/declarations.py +0 -0
  116. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/duplication.py +0 -0
  117. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/evidence.py +0 -0
  118. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/git_tools.py +0 -0
  119. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/history.py +0 -0
  120. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/idioms.py +0 -0
  121. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/instructions.py +0 -0
  122. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/mcp_server.py +0 -0
  123. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/metrics.py +0 -0
  124. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/renderers.py +0 -0
  125. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/report.py +0 -0
  126. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/sarif.py +0 -0
  127. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/scoring.py +0 -0
  128. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/similarity.py +0 -0
  129. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/src/maintainability_audit/source.py +0 -0
  130. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_acquisition_trust.py +0 -0
  131. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_adapter_docs.py +0 -0
  132. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_adapter_recordings.py +0 -0
  133. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_adapters.py +0 -0
  134. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_added_lines.py +0 -0
  135. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_analysis_coverage.py +0 -0
  136. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_analysis_execution.py +0 -0
  137. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_analyzer_bridge.py +0 -0
  138. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_analyzer_config_isolation.py +0 -0
  139. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_analyzer_estimate_claims.py +0 -0
  140. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_analyzer_pinning.py +0 -0
  141. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_analyzer_primary_estimate.py +0 -0
  142. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_analyzer_provenance.py +0 -0
  143. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_analyzer_provenance_exclusions.py +0 -0
  144. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_analyzer_scoring_path.py +0 -0
  145. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_analyzer_spread_range.py +0 -0
  146. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_analyzer_xml_bounds.py +0 -0
  147. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_annotations_match_behaviour.py +0 -0
  148. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_anticipated_refusals.py +0 -0
  149. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_architecture.py +0 -0
  150. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_artifact_write_route_class.py +0 -0
  151. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_aspect_coverage.py +0 -0
  152. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_asset_snapshot_class.py +0 -0
  153. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_ast_reading_resolves_spellings.py +0 -0
  154. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_audit_components.py +0 -0
  155. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_authorization_freshness.py +0 -0
  156. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_authorship_gates.py +0 -0
  157. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_backfill.py +0 -0
  158. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_band_concealment_class.py +0 -0
  159. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_band_concealment_p3.py +0 -0
  160. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_band_pressures.py +0 -0
  161. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_bands.py +0 -0
  162. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_body_or_signature_class.py +0 -0
  163. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_c_declarations.py +0 -0
  164. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_calibration_corpus.py +0 -0
  165. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_calibration_drift.py +0 -0
  166. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_calibration_tooling.py +0 -0
  167. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_catalog_selection.py +0 -0
  168. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_catalog_tier_promise.py +0 -0
  169. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_chat_primary_docs.py +0 -0
  170. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_checkstyle_adapter.py +0 -0
  171. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_checkstyle_catalog.py +0 -0
  172. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_ci_history_recipe.py +0 -0
  173. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_ci_installs_the_analyzer_pool.py +0 -0
  174. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_claimed_languages.py +0 -0
  175. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_cli.py +0 -0
  176. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_clone_group_class.py +0 -0
  177. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_cognitive_complexity.py +0 -0
  178. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_cohort_statistics.py +0 -0
  179. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_concealment_properties.py +0 -0
  180. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_config_shape.py +0 -0
  181. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_consent_and_grant.py +0 -0
  182. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_consumer_migration.py +0 -0
  183. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_contract_claims.py +0 -0
  184. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_corroborate.py +0 -0
  185. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_coverage_by_language.py +0 -0
  186. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_cpp_declarations.py +0 -0
  187. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_csharp_declarations.py +0 -0
  188. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_d15_composition.py +0 -0
  189. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_d15_goal_directed.py +0 -0
  190. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_dead_code.py +0 -0
  191. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_declaration_edge_cases.py +0 -0
  192. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_declaration_grading.py +0 -0
  193. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_declaration_ranges.py +0 -0
  194. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_declaration_source.py +0 -0
  195. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_declared_authorship.py +0 -0
  196. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_declared_imports.py +0 -0
  197. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_derivation_mix.py +0 -0
  198. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_determinism.py +0 -0
  199. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_direct_runner_availability_class.py +0 -0
  200. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_discovery.py +0 -0
  201. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_doc_claims.py +0 -0
  202. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_docs_links.py +0 -0
  203. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_economic_context.py +0 -0
  204. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_empty_output_not_clean.py +0 -0
  205. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_empty_output_not_clean_adapters.py +0 -0
  206. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_environment_work_order.py +0 -0
  207. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_evidence_invariants.py +0 -0
  208. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_evidence_normalization.py +0 -0
  209. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_evidence_properties.py +0 -0
  210. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_exclusion_dialects.py +0 -0
  211. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_falsifier_standard.py +0 -0
  212. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_file_list_cap_class.py +0 -0
  213. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_finding_identity.py +0 -0
  214. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_first_run_elicitation.py +0 -0
  215. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_first_run_prompt.py +0 -0
  216. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_first_run_reelicitation.py +0 -0
  217. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_fix_breadth_window.py +0 -0
  218. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_fixed_form_fortran.py +0 -0
  219. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_format_ask.py +0 -0
  220. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_fortran_declarations.py +0 -0
  221. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_fortran_metrics.py +0 -0
  222. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_fortran_practice.py +0 -0
  223. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_git_argv.py +0 -0
  224. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_git_read_only.py +0 -0
  225. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_git_readonly_class.py +0 -0
  226. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_grant_only_user_tier.py +0 -0
  227. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_history.py +0 -0
  228. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_history_boundary.py +0 -0
  229. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_history_manifest.py +0 -0
  230. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_history_schema2.py +0 -0
  231. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_history_window.py +0 -0
  232. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_hostile_prompt.py +0 -0
  233. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_html_executive.py +0 -0
  234. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_html_section_parity_class.py +0 -0
  235. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_identity_docs.py +0 -0
  236. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_idiom_divergence.py +0 -0
  237. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_installed_wheel_runtime.py +0 -0
  238. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_instruction_targets.py +0 -0
  239. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_java_range_guard.py +0 -0
  240. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_java_ranges.py +0 -0
  241. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_java_scored.py +0 -0
  242. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_java_wiring.py +0 -0
  243. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_js_complexity_operators.py +0 -0
  244. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_js_declarations.py +0 -0
  245. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_language_coverage.py +0 -0
  246. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_mcp_baseline_payload.py +0 -0
  247. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_mcp_history.py +0 -0
  248. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_mcp_server.py +0 -0
  249. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_migration_1_0.py +0 -0
  250. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_near_duplicates.py +0 -0
  251. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_network_disclosure.py +0 -0
  252. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_one_setup_across_surfaces_class.py +0 -0
  253. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_pattern_budget.py +0 -0
  254. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_phase6_claims.py +0 -0
  255. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_pillars_and_practice.py +0 -0
  256. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_platform_claim.py +0 -0
  257. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_pmd_adapter.py +0 -0
  258. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_pmd_audit_closures.py +0 -0
  259. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_pool_runs_by_config.py +0 -0
  260. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_population_floors.py +0 -0
  261. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_practice_nested_gates_class.py +0 -0
  262. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_presentation_refusals.py +0 -0
  263. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_promises.py +0 -0
  264. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_prompt_escalation_once.py +0 -0
  265. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_prompt_honesty.py +0 -0
  266. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_prompt_location_escalation_class.py +0 -0
  267. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_prove_falsifiers.py +0 -0
  268. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_python_declarations.py +0 -0
  269. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_read_boundary.py +0 -0
  270. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_readme_claims.py +0 -0
  271. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_recurrence.py +0 -0
  272. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_release_plan.py +0 -0
  273. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_release_plan_status.py +0 -0
  274. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_report_remedy_copy.py +0 -0
  275. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_roles_recorded.py +0 -0
  276. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_root_grants.py +0 -0
  277. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_rubric_ignores_project_lint_config.py +0 -0
  278. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_runner.py +0 -0
  279. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_sarif.py +0 -0
  280. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_scan_history.py +0 -0
  281. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_scan_scope.py +0 -0
  282. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_scanning.py +0 -0
  283. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_schema_fidelity.py +0 -0
  284. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_scoring_calibration.py +0 -0
  285. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_semantic_policy.py +0 -0
  286. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_setup_gate_completeness.py +0 -0
  287. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_setup_gate_independent_contract.py +0 -0
  288. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_setup_precondition.py +0 -0
  289. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_skill_install.py +0 -0
  290. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_source_index.py +0 -0
  291. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_spotbugs_adapter.py +0 -0
  292. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_spotbugs_catalog.py +0 -0
  293. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_stage8_contract.py +0 -0
  294. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_tdd_structure.py +0 -0
  295. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_test_execution_class.py +0 -0
  296. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_three_presentations.py +0 -0
  297. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_trends.py +0 -0
  298. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_type_fields_not_functions_class.py +0 -0
  299. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_uat_falsifiers.py +0 -0
  300. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_unread_code.py +0 -0
  301. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_user_config_tier.py +0 -0
  302. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_verification_audit_closures.py +0 -0
  303. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_verified_grade.py +0 -0
  304. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_version_claim.py +0 -0
  305. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_wheel_contents.py +0 -0
  306. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_work_order.py +0 -0
  307. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_work_order_items.py +0 -0
  308. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_work_order_selection.py +0 -0
  309. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_workflow_supply_chain.py +0 -0
  310. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_write_boundary.py +0 -0
  311. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_write_route_class.py +0 -0
  312. {maintainability_agent-2.1.0 → maintainability_agent-2.3.0}/tests/test_written_record.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maintainability-agent
3
- Version: 2.1.0
3
+ Version: 2.3.0
4
4
  Summary: Maintainability gate and AI remediation prompt generator for CI.
5
5
  Author: Marshall Guillory
6
6
  License: MIT
@@ -44,7 +44,7 @@ Dynamic: license-file
44
44
  **A deterministic, offline maintainability audit whose output is a _bounded
45
45
  work order_ for an AI coding agent** — a copy-paste prompt, per finding, that
46
46
  says *fix exactly these and refactor nothing else*. Chat-primary; CLI for CI.
47
- Version **2.1.0**.
47
+ Version **2.3.0**.
48
48
 
49
49
  **Languages parsed:** Python, Java, C, C++, C#, Fortran (free-form *and*
50
50
  fixed-form), and the JS/TS family — each by a scanner written for it, and
@@ -5,7 +5,7 @@
5
5
  **A deterministic, offline maintainability audit whose output is a _bounded
6
6
  work order_ for an AI coding agent** — a copy-paste prompt, per finding, that
7
7
  says *fix exactly these and refactor nothing else*. Chat-primary; CLI for CI.
8
- Version **2.1.0**.
8
+ Version **2.3.0**.
9
9
 
10
10
  **Languages parsed:** Python, Java, C, C++, C#, Fortran (free-form *and*
11
11
  fixed-form), and the JS/TS family — each by a scanner written for it, and
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "maintainability-agent"
7
- version = "2.1.0"
7
+ version = "2.3.0"
8
8
  description = "Maintainability gate and AI remediation prompt generator for CI."
9
9
  readme = "README.md"
10
10
  # 3.11, not 3.10. `_discovery`, `_pillars` and `_runner` all import
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maintainability-agent
3
- Version: 2.1.0
3
+ Version: 2.3.0
4
4
  Summary: Maintainability gate and AI remediation prompt generator for CI.
5
5
  Author: Marshall Guillory
6
6
  License: MIT
@@ -44,7 +44,7 @@ Dynamic: license-file
44
44
  **A deterministic, offline maintainability audit whose output is a _bounded
45
45
  work order_ for an AI coding agent** — a copy-paste prompt, per finding, that
46
46
  says *fix exactly these and refactor nothing else*. Chat-primary; CLI for CI.
47
- Version **2.1.0**.
47
+ Version **2.3.0**.
48
48
 
49
49
  **Languages parsed:** Python, Java, C, C++, C#, Fortran (free-form *and*
50
50
  fixed-form), and the JS/TS family — each by a scanner written for it, and
@@ -12,6 +12,7 @@ src/maintainability_audit/__main__.py
12
12
  src/maintainability_audit/_adapters.py
13
13
  src/maintainability_audit/_analysis.py
14
14
  src/maintainability_audit/_aspects.py
15
+ src/maintainability_audit/_attestation.py
15
16
  src/maintainability_audit/_backfill.py
16
17
  src/maintainability_audit/_bands.py
17
18
  src/maintainability_audit/_built_ins.py
@@ -33,6 +34,7 @@ src/maintainability_audit/_evidence_view.py
33
34
  src/maintainability_audit/_finding_match.py
34
35
  src/maintainability_audit/_first_run.py
35
36
  src/maintainability_audit/_formula.py
37
+ src/maintainability_audit/_gates.py
36
38
  src/maintainability_audit/_generic.py
37
39
  src/maintainability_audit/_grant_ledger.py
38
40
  src/maintainability_audit/_history_view.py
@@ -62,6 +64,7 @@ src/maintainability_audit/_ranges_csharp.py
62
64
  src/maintainability_audit/_ranges_fortran.py
63
65
  src/maintainability_audit/_ranges_java.py
64
66
  src/maintainability_audit/_ranges_js.py
67
+ src/maintainability_audit/_ratchet.py
65
68
  src/maintainability_audit/_recurrence.py
66
69
  src/maintainability_audit/_runner.py
67
70
  src/maintainability_audit/_safe_write.py
@@ -142,6 +145,7 @@ tests/test_artifact_write_route_class.py
142
145
  tests/test_aspect_coverage.py
143
146
  tests/test_asset_snapshot_class.py
144
147
  tests/test_ast_reading_resolves_spellings.py
148
+ tests/test_attestation.py
145
149
  tests/test_audit_components.py
146
150
  tests/test_authorization_freshness.py
147
151
  tests/test_authorship_gates.py
@@ -187,6 +191,7 @@ tests/test_declared_authorship.py
187
191
  tests/test_declared_imports.py
188
192
  tests/test_derivation_mix.py
189
193
  tests/test_determinism.py
194
+ tests/test_dimension_ratchet.py
190
195
  tests/test_direct_runner_availability_class.py
191
196
  tests/test_discovery.py
192
197
  tests/test_doc_claims.py
@@ -1,3 +1,3 @@
1
1
  """Maintainability Audit CI."""
2
2
 
3
- __version__ = "2.1.0"
3
+ __version__ = "2.3.0"
@@ -0,0 +1,166 @@
1
+ """The record a generator cannot produce about its own output.
2
+
3
+ The three mechanisms — scope conformance, suppression detection, the
4
+ dimension ratchet — each answer one question about a change. This composes
5
+ them into the artifact they were built for: a per-change record of what was
6
+ measured, what the agent was told to change, what it actually changed, and
7
+ what moved.
8
+
9
+ **Why an independent process has to emit it.** A platform that generates
10
+ code and then reports on its quality is producing a self-assessment. That
11
+ is a property of the arrangement, not a criticism of any particular one,
12
+ and no amount of engineering removes it. The question a reviewer asks is
13
+ *who checked this, and can they re-derive the check months from now* — and
14
+ an author cannot be the answer. This tool writes nothing, runs no model,
15
+ and returns the same verdict from the same inputs, which is the only reason
16
+ its answer can be evidence rather than an opinion.
17
+
18
+ **It is reproducible, not signed.** Every field is derived from the report
19
+ and the pinned inputs, and the digest at the end is over the record's own
20
+ content, so two runs on the same commit with the same analyzer versions
21
+ produce the same digest. That makes it *verifiable* — anyone can re-run and
22
+ compare. It is deliberately not called a signature: nothing here holds a
23
+ key, and claiming a cryptographic guarantee this does not provide would be
24
+ exactly the kind of overclaim the rest of the project spends its time
25
+ removing. Signing the artifact, if a caller wants that, is a step they take
26
+ with their own key on a file whose bytes are stable.
27
+
28
+ **What it does not establish.** The record says which files the work order
29
+ named and whether a checker was switched off — not whether the edits were
30
+ correct. It never opens the diff to judge it, and it never touches a score.
31
+ Review is still review.
32
+ """
33
+
34
+ from __future__ import annotations
35
+
36
+ import hashlib
37
+ import json
38
+ from typing import Any
39
+
40
+ ARTIFACT_VERSION = 1
41
+
42
+
43
+ def _verdicts(report: dict[str, Any]) -> list[str]:
44
+ """One line per question that was actually asked."""
45
+ lines: list[str] = []
46
+ conformance = report.get("scope_conformance")
47
+ if conformance:
48
+ out = len(conformance["out_of_scope"])
49
+ silenced = conformance["suppressions_on_named_paths"]
50
+ lines.append(
51
+ f"- **Stayed inside the work order:** "
52
+ f"{'yes' if conformance['conformant'] else f'no — {out} file(s) outside it'}"
53
+ )
54
+ lines.append(
55
+ f"- **Without silencing a finding:** "
56
+ f"{'yes' if not silenced else f'no — {silenced} suppression(s) on flagged files'}"
57
+ )
58
+ ratchet = report.get("dimension_ratchet")
59
+ if ratchet:
60
+ if not ratchet["comparable"]:
61
+ lines.append(
62
+ f"- **No dimension regressed:** not established — {ratchet['reason']}"
63
+ )
64
+ elif ratchet["regressed"]:
65
+ names = ", ".join(item["name"] for item in ratchet["regressed"])
66
+ lines.append(f"- **No dimension regressed:** no — {names}")
67
+ else:
68
+ lines.append("- **No dimension regressed:** yes")
69
+ return lines
70
+
71
+
72
+ def _measured(report: dict[str, Any]) -> list[str]:
73
+ score = report.get("score") or {}
74
+ coverage = report.get("analyzer_coverage") or {}
75
+ status = score.get("evidence_status")
76
+ if isinstance(status, dict):
77
+ status = status.get("status")
78
+ ran = (coverage.get("by_outcome") or {}).get("ran") or []
79
+ return [
80
+ f"- Estimate: {score.get('maintainability_estimate', 'withheld')}",
81
+ f"- Verified grade: {score.get('verified_grade') or 'withheld'}",
82
+ f"- Evidence: {status or 'unstated'}",
83
+ f"- Analyzers that ran: {len(ran)}",
84
+ f"- Hard gate failures: {len(report.get('hard_gate_failures') or [])}",
85
+ ]
86
+
87
+
88
+ def attestation_record(report: dict[str, Any]) -> dict[str, Any]:
89
+ """The structured record, before rendering."""
90
+ conformance = report.get("scope_conformance") or {}
91
+ ratchet = report.get("dimension_ratchet") or {}
92
+ score = report.get("score") or {}
93
+ record = {
94
+ "artifact_version": ARTIFACT_VERSION,
95
+ "tool_version": report.get("tool_version"),
96
+ "commit": report.get("git_commit"),
97
+ "branch": report.get("git_branch"),
98
+ # A finding reported against "the commit" that actually depends on
99
+ # uncommitted work is unreproducible, so the state of the tree is
100
+ # part of what is attested rather than a footnote.
101
+ "tree_dirty": bool(report.get("git_status_short")),
102
+ "revspec": conformance.get("revspec"),
103
+ "estimate": score.get("maintainability_estimate"),
104
+ "verified_grade": score.get("verified_grade"),
105
+ "stayed_in_scope": conformance.get("conformant"),
106
+ "no_finding_silenced": (
107
+ None if not conformance else conformance["suppressions_on_named_paths"] == 0
108
+ ),
109
+ "no_dimension_regressed": (
110
+ None if not ratchet or not ratchet.get("comparable")
111
+ else not ratchet["regressed"]
112
+ ),
113
+ "scope_conformance": conformance or None,
114
+ "dimension_ratchet": ratchet or None,
115
+ }
116
+ # The digest is over the record without it, so it is reproducible: the
117
+ # same commit measured with the same analyzer versions yields the same
118
+ # value, and anyone can recompute it rather than trust it.
119
+ record["digest"] = hashlib.sha256(
120
+ json.dumps(record, sort_keys=True, default=str).encode("utf-8")
121
+ ).hexdigest()[:16]
122
+ return record
123
+
124
+
125
+ def render_attestation(report: dict[str, Any]) -> str:
126
+ """The record as text, for a reviewer or an audit trail."""
127
+ record = attestation_record(report)
128
+ dirty = " (uncommitted changes present)" if record["tree_dirty"] else ""
129
+ lines = [
130
+ "# Maintainability attestation",
131
+ "",
132
+ "An independent, deterministic check on a change. Produced by a "
133
+ "process that generated none of the code it examined.",
134
+ "",
135
+ f"- Commit: `{record['commit'] or 'unknown'}`{dirty}",
136
+ f"- Branch: `{record['branch'] or 'unknown'}`",
137
+ f"- Tool version: {record['tool_version'] or 'unknown'}",
138
+ f"- Compared: `{record['revspec'] or 'not asked'}`",
139
+ "",
140
+ "## What was measured",
141
+ "",
142
+ *_measured(report),
143
+ "",
144
+ "## What the change did",
145
+ "",
146
+ ]
147
+ verdicts = _verdicts(report)
148
+ lines += verdicts or ["- Nothing was asked: run with `--conformance` and "
149
+ "`--fail-on-regression` to populate this section."]
150
+ lines += [
151
+ "",
152
+ "## What this does not establish",
153
+ "",
154
+ "- It compares paths and reads added lines. A file in scope means the "
155
+ "work order named it, **not** that the change to it was correct — this "
156
+ "never opens the diff to judge it.",
157
+ "- It touches no score. Whether a change obeyed its instructions is a "
158
+ "fact about behaviour, not evidence about the code's condition.",
159
+ "- It is reproducible, **not signed**. The digest is over this "
160
+ "record's own content, so the same inputs reproduce it and anyone can "
161
+ "recompute it. Nothing here holds a key.",
162
+ "",
163
+ f"Digest: `{record['digest']}`",
164
+ "",
165
+ ]
166
+ return "\n".join(lines)
@@ -0,0 +1,122 @@
1
+ """What happens after the audit and before the exit code.
2
+
3
+ Split from `cli.py` when it crossed this project's 500-line file gate —
4
+ the same reason `_mcp_setup`, `_adapters` and `test_architecture` were
5
+ split before it.
6
+
7
+ The split is not arbitrary. Everything here composes from the **finished
8
+ report**: whether the diff obeyed its work order, whether a dimension
9
+ slipped since the last comparable scan, and what those two facts mean for
10
+ the process exit code. None of it measures a tree and none of it may reach
11
+ scoring — a fact about an agent's behaviour, or about the difference
12
+ between two scans, is not evidence about the code's condition.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import argparse
18
+ import sys
19
+ from pathlib import Path
20
+
21
+ from .baseline import findings_not_in_baseline
22
+ from .git_tools import added_lines, changed_paths
23
+
24
+
25
+ def attach_conformance(args: argparse.Namespace, report: dict) -> None:
26
+ """Record how the diff relates to the work order, when asked.
27
+
28
+ Assembly, not scoring: the record is attached to the report and reaches
29
+ no dimension, because whether a diff was obedient is a fact about an
30
+ agent's behaviour and not evidence about the code's condition.
31
+ """
32
+ if not args.conformance:
33
+ return
34
+ from ._conformance import scope_conformance
35
+
36
+ root = Path(report["root"])
37
+ changed = changed_paths(root, args.conformance)
38
+ added = added_lines(root, args.conformance)
39
+ report["scope_conformance"] = scope_conformance(
40
+ report, changed, args.conformance, added
41
+ )
42
+
43
+
44
+ def attach_ratchet(report: dict, history_path: Path) -> None:
45
+ """Record whether any category slipped since the last comparable scan."""
46
+ from ._ratchet import dimension_ratchet
47
+ from ._scan_history import read_history
48
+
49
+ if not history_path.exists():
50
+ return
51
+ report["dimension_ratchet"] = dimension_ratchet(read_history(history_path))
52
+
53
+
54
+ def _attach_post_audit_records(
55
+ args: argparse.Namespace, report: dict, history_path: Path
56
+ ) -> None:
57
+ """Records composed from the finished report, before it is rendered.
58
+
59
+ Both read the report rather than the tree, and neither reaches scoring:
60
+ whether a diff obeyed its work order, and whether a dimension slipped
61
+ since the last comparable scan, are facts about a change and its
62
+ history — not evidence about the code's condition.
63
+ """
64
+ attach_conformance(args, report)
65
+ attach_ratchet(report, history_path)
66
+
67
+
68
+ def _conformance_exit(args: argparse.Namespace, report: dict) -> int:
69
+ """0 to continue; otherwise the exit code the conformance gate demands."""
70
+ if not args.fail_on_out_of_scope:
71
+ return 0
72
+ record = report.get("scope_conformance")
73
+ if record is None:
74
+ print("--fail-on-out-of-scope needs --conformance REVSPEC", file=sys.stderr)
75
+ return 2
76
+ # `clean`, not `conformant`: staying in scope while silencing the finding
77
+ # is the evasion the pairing exists to catch, and a gate that accepted it
78
+ # would be satisfied by the thing it guards against.
79
+ return 0 if record["clean"] else 1
80
+
81
+
82
+ def _regression_exit(args: argparse.Namespace, report: dict) -> int:
83
+ """0 to continue; otherwise the exit code the ratchet demands."""
84
+ if not args.fail_on_regression:
85
+ return 0
86
+ ratchet = report.get("dimension_ratchet")
87
+ if ratchet is None:
88
+ print("--fail-on-regression needs a recorded history (--record-history)",
89
+ file=sys.stderr)
90
+ return 2
91
+ # A run that could not compare does not pass quietly: exiting 0 where the
92
+ # question was never asked is the "absence read as a pass" shape.
93
+ #
94
+ # The reason is not interpolated — it derives from recorded scan history,
95
+ # and code scanning is right that history fields have no business in a log
96
+ # line. It stays in the report, which a caller reads deliberately.
97
+ # Suppressing that alert was refused: this release ships the check that
98
+ # treats a suppression as a finding rather than a fix.
99
+ if not ratchet["comparable"]:
100
+ print("no comparable scan for --fail-on-regression; see "
101
+ "dimension_ratchet.reason in the report", file=sys.stderr)
102
+ return 2
103
+ return 1 if ratchet["regressed"] else 0
104
+
105
+
106
+ def audit_exit_code(args: argparse.Namespace, report: dict) -> int:
107
+ if args.fail_on_new:
108
+ # Structured matching, never a label-set difference: a label
109
+ # changes under `git mv` and same-name reorder, and failing a
110
+ # build over either is a false report about the change.
111
+ root = Path(report["root"])
112
+ if findings_not_in_baseline(report, args.baseline, root):
113
+ return 1
114
+ if args.fail_on_gate and report["hard_gate_failures"]:
115
+ return 1
116
+ conformance = _conformance_exit(args, report)
117
+ if conformance:
118
+ return conformance
119
+ regression = _regression_exit(args, report)
120
+ if regression:
121
+ return regression
122
+ return 0
@@ -0,0 +1,109 @@
1
+ """Did any dimension get worse than the last comparable scan?
2
+
3
+ The third mechanism behind the attestation. `--fail-on-new` already catches
4
+ a finding that clears and returns, using structured identity so a rename is
5
+ not a new finding. What nothing caught is the slower failure: a change that
6
+ improves one dimension while quietly regressing another. Both scans are
7
+ green by every existing gate, the overall estimate can even rise, and the
8
+ thing that got worse is never named.
9
+
10
+ **The comparison is only honest inside one segment.** History is already
11
+ split wherever the instrument changed — a different calibration constant, a
12
+ different analyzer population — because two scans taken with different
13
+ instruments cannot be subtracted. `_scan_history.segments` owns that rule,
14
+ and this module refuses rather than reaching across a break. A ratchet that
15
+ compared a 1.x score to a 2.0 score would have reported every repository on
16
+ earth as catastrophically regressed on the day the corpus was extended, and
17
+ it would have been the ratchet that was wrong.
18
+
19
+ So there are three outcomes, not two: **held**, **regressed**, and **not
20
+ comparable**. The third is not a pass. It says the question could not be
21
+ asked, which is what a reader needs in order to know they are not being
22
+ reassured by silence.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ from typing import Any
28
+
29
+ from ._scan_history import ScanRecord, segments
30
+
31
+ #: Scores are reported to one decimal, so a difference below this is the
32
+ #: rounding boundary rather than a movement. Named rather than inlined
33
+ #: because it is a judgment: too tight and every scan trips on noise, too
34
+ #: loose and a real slide hides under the threshold.
35
+ TOLERANCE = 0.05
36
+
37
+
38
+ def _series(record: ScanRecord, field: str) -> dict[str, float]:
39
+ values = getattr(record, field, None) or {}
40
+ return {
41
+ name: float(value) for name, value in values.items()
42
+ if isinstance(value, (int, float))
43
+ }
44
+
45
+
46
+ def _regressions(
47
+ previous: ScanRecord, current: ScanRecord, field: str
48
+ ) -> list[dict[str, Any]]:
49
+ """Names that dropped, with the size of the drop."""
50
+ before, after = _series(previous, field), _series(current, field)
51
+ found = []
52
+ for name, was in sorted(before.items()):
53
+ # A name the current scan does not carry is not a regression: the
54
+ # aspect became unmeasurable, which the evidence model already
55
+ # reports as `Unknown` rather than as a zero. Scoring it as a drop
56
+ # to nothing would turn withheld evidence into a failure, which is
57
+ # the exact inversion P3 forbids.
58
+ if name not in after:
59
+ continue
60
+ now = after[name]
61
+ if was - now > TOLERANCE:
62
+ found.append({
63
+ "name": name, "was": round(was, 2),
64
+ "now": round(now, 2), "drop": round(was - now, 2),
65
+ })
66
+ return found
67
+
68
+
69
+ def dimension_ratchet(records: list[ScanRecord]) -> dict[str, Any]:
70
+ """Compare the newest scan with the previous comparable one.
71
+
72
+ `comparable` is the load-bearing field. False means the two scans sit
73
+ either side of an instrument change, or there is no earlier scan at
74
+ all — in both cases the question was not asked, and a caller must not
75
+ read that as "nothing regressed".
76
+ """
77
+ if len(records) < 2:
78
+ return {
79
+ "comparable": False,
80
+ "reason": "no earlier scan to compare against",
81
+ "regressed": [],
82
+ }
83
+
84
+ latest = segments(records)[-1]
85
+ if len(latest.records) < 2:
86
+ return {
87
+ "comparable": False,
88
+ "reason": (
89
+ latest.break_reason
90
+ or "the previous scan was taken with a different instrument"
91
+ ),
92
+ "regressed": [],
93
+ }
94
+
95
+ previous, current = latest.records[-2], latest.records[-1]
96
+ categories = _regressions(previous, current, "categories")
97
+ aspects = _regressions(previous, current, "aspects")
98
+
99
+ return {
100
+ "comparable": True,
101
+ "against": getattr(previous, "commit", None),
102
+ "categories": categories,
103
+ "aspects": aspects,
104
+ # Categories only. An aspect can move while the category holding it
105
+ # holds, and failing on every aspect wobble makes the gate noise.
106
+ # Aspects are reported so a reader can see what moved underneath.
107
+ "regressed": categories,
108
+ "held": not categories,
109
+ }
@@ -14,6 +14,10 @@ from ._first_run import (
14
14
  maybe_prompt_first_run,
15
15
  maybe_prompt_test_command,
16
16
  )
17
+ from ._gates import (
18
+ _attach_post_audit_records,
19
+ audit_exit_code,
20
+ )
17
21
  from ._mcp_audit import record_scan_and_attach
18
22
  from ._safe_write import write_artifact
19
23
  from ._scan_history import (
@@ -24,7 +28,6 @@ from ._user_config import mark_repo_seen
24
28
  from ._work_order import SELECTABLE, combined_delta, select
25
29
  from .baseline import (
26
30
  finding_fingerprints,
27
- findings_not_in_baseline,
28
31
  load_baseline,
29
32
  write_baseline,
30
33
  )
@@ -36,7 +39,7 @@ from .config import (
36
39
  load_config,
37
40
  repository_path,
38
41
  )
39
- from .git_tools import added_lines, changed_paths
42
+ from .git_tools import changed_paths
40
43
  from .instructions import (
41
44
  INSTRUCTION_TARGETS,
42
45
  UnknownTarget,
@@ -65,6 +68,14 @@ def _add_gate_arguments(parser: argparse.ArgumentParser) -> None:
65
68
  "else'; without this the bound is an instruction nothing checks. "
66
69
  "Reports only unless --fail-on-out-of-scope is passed.",
67
70
  )
71
+ parser.add_argument(
72
+ "--fail-on-regression", action="store_true",
73
+ help="Exit 1 when any scoring category is lower than the previous "
74
+ "comparable scan. Reads the recorded history, and refuses to "
75
+ "compare across an instrument change rather than reporting a "
76
+ "false regression: two scans taken under different calibration "
77
+ "cannot be subtracted.",
78
+ )
68
79
  parser.add_argument(
69
80
  "--fail-on-out-of-scope", action="store_true",
70
81
  help="With --conformance, exit 1 when the diff touched files the work "
@@ -86,6 +97,14 @@ def add_arguments(parser: argparse.ArgumentParser) -> None:
86
97
  parser.add_argument("--prompt-output", help="Optional Markdown prompt for AI-assisted remediation.")
87
98
  parser.add_argument("--comment-output", help="Optional Markdown body suitable for a PR comment.")
88
99
  parser.add_argument("--agent-instructions-output", help="Optional reusable instructions for AI coding agents.")
100
+ parser.add_argument(
101
+ "--attestation-output",
102
+ help="Write the attestation: an independent, reproducible record of "
103
+ "what was measured, what the change was told to do, what it did, "
104
+ "and what moved. Populated by --conformance and "
105
+ "--fail-on-regression; a generator cannot produce this about its "
106
+ "own output.",
107
+ )
89
108
  parser.add_argument("--hostile-prompt-output",
90
109
  help="Optional adversarial audit brief seeded from this run (ADR 013). "
91
110
  "Text only: it does not gate, score, or send anything.")
@@ -216,6 +235,10 @@ def write_outputs(args: argparse.Namespace, report: dict, rendered: str) -> None
216
235
  write_artifact(root, Path(args.comment_output), render_pr_comment(report) + "\n")
217
236
  if args.agent_instructions_output:
218
237
  write_artifact(root, Path(args.agent_instructions_output), render_agent_instructions(report) + "\n")
238
+ if args.attestation_output:
239
+ from ._attestation import render_attestation
240
+
241
+ write_artifact(root, Path(args.attestation_output), render_attestation(report))
219
242
  if args.hostile_prompt_output:
220
243
  write_artifact(root, Path(args.hostile_prompt_output),
221
244
  render_hostile_audit_prompt(report) + "\n")
@@ -226,48 +249,6 @@ def write_outputs(args: argparse.Namespace, report: dict, rendered: str) -> None
226
249
  json.dumps(report_to_sarif(report), indent=2) + "\n", json_artifact=True)
227
250
 
228
251
 
229
- def attach_conformance(args: argparse.Namespace, report: dict) -> None:
230
- """Record how the diff relates to the work order, when asked.
231
-
232
- Assembly, not scoring: the record is attached to the report and reaches
233
- no dimension, because whether a diff was obedient is a fact about an
234
- agent's behaviour and not evidence about the code's condition.
235
- """
236
- if not args.conformance:
237
- return
238
- from ._conformance import scope_conformance
239
-
240
- root = Path(report["root"])
241
- changed = changed_paths(root, args.conformance)
242
- added = added_lines(root, args.conformance)
243
- report["scope_conformance"] = scope_conformance(
244
- report, changed, args.conformance, added
245
- )
246
-
247
-
248
- def audit_exit_code(args: argparse.Namespace, report: dict) -> int:
249
- if args.fail_on_new:
250
- # Structured matching, never a label-set difference: a label
251
- # changes under `git mv` and same-name reorder, and failing a
252
- # build over either is a false report about the change.
253
- root = Path(report["root"])
254
- if findings_not_in_baseline(report, args.baseline, root):
255
- return 1
256
- if args.fail_on_gate and report["hard_gate_failures"]:
257
- return 1
258
- if args.fail_on_out_of_scope:
259
- record = report.get("scope_conformance")
260
- if record is None:
261
- print("--fail-on-out-of-scope needs --conformance REVSPEC", file=sys.stderr)
262
- return 2
263
- # `clean`, not `conformant`: staying in scope while silencing the
264
- # finding is the evasion the pairing exists to catch, and a gate
265
- # that accepted it would be satisfied by the thing it guards against.
266
- if not record["clean"]:
267
- return 1
268
- return 0
269
-
270
-
271
252
  def _analyzers_resolved(args: argparse.Namespace, config: dict) -> bool:
272
253
  """Whether the pool runs: flags beat config, config beats nothing.
273
254
 
@@ -325,7 +306,6 @@ def _install_skill_action(args: argparse.Namespace) -> int:
325
306
  return 0
326
307
 
327
308
 
328
-
329
309
  def _target_paths(pairs: list[str] | None) -> dict[str, str]:
330
310
  """`TARGET=PATH` arguments, as a mapping.
331
311
 
@@ -429,7 +409,7 @@ def main(argv: list[str] | None = None) -> int:
429
409
  record=bool(record), want_targets=bool(args.prompt_output))
430
410
  # Before rendering, so the record is in the report every presentation
431
411
  # and every consumer reads, rather than only in the exit code.
432
- attach_conformance(args, report)
412
+ _attach_post_audit_records(args, report, history_path)
433
413
  rendered = _render_presentation(args, report, history_path)
434
414
  write_outputs(args, report, rendered)
435
415
  mark_repo_seen(root) # completed audit: not a first run now, whatever the gate says (D13)
@@ -11,7 +11,7 @@ from ._config_defaults import DEFAULT_CONFIG, DEFAULT_IDIOM_GROUPS
11
11
  # still the door every caller comes through.
12
12
  __all__ = ["DEFAULT_CONFIG", "DEFAULT_IDIOM_GROUPS", "PROJECT_URL", "VERSION"]
13
13
 
14
- VERSION = "2.1.0"
14
+ VERSION = "2.3.0"
15
15
 
16
16
  PROJECT_URL = "https://github.com/marshallguillory86/maintainability-agent"
17
17
 
@@ -11,6 +11,7 @@ from __future__ import annotations
11
11
  from typing import Any
12
12
 
13
13
  from . import _evidence_view as view
14
+ from ._attestation import render_attestation # noqa: F401
14
15
  from ._hostile_prompt import render_hostile_audit_prompt # noqa: F401
15
16
  from ._hotspots import hotspot_measure, hotspot_name
16
17
  from ._tdd_view import tdd_sentences